"use server";

import { revalidatePath } from "next/cache";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { db } from "@/db";
import {
  projects,
  clients,
  payments,
  project_offers,
  offer_tier_services,
  services,
  phases,
  tasks,
} from "@/db/schema";
import { eq, asc } from "drizzle-orm";
import { z } from "zod";
import { nanoid } from "nanoid";

async function requireAdmin() {
  const session = await getServerSession(authOptions);
  if (!session) throw new Error("Non autorizzato");
}

export async function createProject(fd: FormData): Promise<{ projectId: string }> {
  await requireAdmin();
  const name = String(fd.get("name") ?? "").trim();
  const clientId = String(fd.get("client_id") ?? "").trim();

  if (!name) throw new Error("Nome progetto obbligatorio");
  if (!clientId) throw new Error("Cliente obbligatorio");

  const clientRows = await db
    .select({ id: clients.id })
    .from(clients)
    .where(eq(clients.id, clientId))
    .limit(1);
  if (clientRows.length === 0) throw new Error("Cliente non trovato");

  const id = nanoid();
  await db.insert(projects).values({ id, client_id: clientId, name });

  revalidatePath("/admin/projects");
  revalidatePath(`/admin/clients/${clientId}`);
  return { projectId: id };
}

export async function archiveProject(projectId: string): Promise<void> {
  await requireAdmin();
  await db.update(projects).set({ archived: true }).where(eq(projects.id, projectId));
  revalidatePath("/admin/projects");
}

export async function unarchiveProject(projectId: string): Promise<void> {
  await requireAdmin();
  await db.update(projects).set({ archived: false }).where(eq(projects.id, projectId));
  revalidatePath("/admin/projects");
}

export async function updateProjectAcceptedTotal(projectId: string, acceptedTotal: string): Promise<void> {
  await requireAdmin();
  await db.update(projects).set({ accepted_total: acceptedTotal }).where(eq(projects.id, projectId));
  revalidatePath(`/admin/projects/${projectId}`);
}

// Creates Acconto 50% + Saldo 50% stubs for a project that has no payments yet.
// Kept for backward compatibility; prefer setPaymentPlan for new code.
export async function initProjectPayments(projectId: string): Promise<void> {
  await requireAdmin();
  const rows = await db
    .select({ accepted_total: projects.accepted_total })
    .from(projects)
    .where(eq(projects.id, projectId))
    .limit(1);
  if (!rows[0]) throw new Error("Progetto non trovato");
  const total = parseFloat(rows[0].accepted_total ?? "0");
  await setPaymentPlan(projectId, "two", total);
}

// Replaces all payments for a project with a fresh plan at a given total.
// Modes:
//   single → 1 row 100%  "Pagamento unico (100%)"
//   two    → 2 rows 50%/50%  "Acconto 50% (inizio lavori)" / "Saldo 50% (alla consegna)"
//   three  → 3 rows 50%/30%/20%
export async function setPaymentPlan(
  projectId: string,
  mode: "single" | "two" | "three",
  total: number
): Promise<void> {
  await requireAdmin();

  type PaymentDef = { label: string; percent: number };
  const plans: Record<string, PaymentDef[]> = {
    single: [{ label: "Pagamento unico (100%)", percent: 100 }],
    two: [
      { label: "Acconto 50% (inizio lavori)", percent: 50 },
      { label: "Saldo 50% (alla consegna)", percent: 50 },
    ],
    three: [
      { label: "Acconto 50% (inizio lavori)", percent: 50 },
      { label: "30% (post revisioni)", percent: 30 },
      { label: "Saldo 20% (alla consegna)", percent: 20 },
    ],
  };

  const plan = plans[mode];
  if (!plan) throw new Error("Modalità pagamento non valida");

  // Delete existing payments for this project, then insert new ones.
  await db.delete(payments).where(eq(payments.project_id, projectId));
  await db.insert(payments).values(
    plan.map((p) => ({
      project_id: projectId,
      label: p.label,
      percent: p.percent.toFixed(2),
      amount: ((total * p.percent) / 100).toFixed(2),
      status: "da_saldare" as const,
    }))
  );

  revalidatePath(`/admin/projects/${projectId}`);
}

// Splits one payment into two with custom amounts. The original row keeps
// its label (+ " – Rata 1"), a new row is created with the remainder.
export async function splitPayment(
  paymentId: string,
  projectId: string,
  firstAmountStr: string
): Promise<void> {
  await requireAdmin();
  const first = parseFloat(firstAmountStr);
  if (isNaN(first) || first <= 0) throw new Error("Importo non valido");

  const rows = await db.select().from(payments).where(eq(payments.id, paymentId)).limit(1);
  if (!rows[0]) throw new Error("Pagamento non trovato");

  const original = rows[0];
  const total = parseFloat(original.amount);
  const second = parseFloat((total - first).toFixed(2));
  if (second <= 0) throw new Error("Il primo importo deve essere inferiore al totale");

  // Strip any existing " – Rata N" suffix so re-splits stay clean.
  const baseLabel = original.label.replace(/ – Rata \d+$/, "");

  await db
    .update(payments)
    .set({ amount: first.toFixed(2), label: `${baseLabel} – Rata 1` })
    .where(eq(payments.id, paymentId));

  await db.insert(payments).values({
    project_id: projectId,
    label: `${baseLabel} – Rata 2`,
    amount: second.toFixed(2),
    status: original.status,
  });

  revalidatePath(`/admin/projects/${projectId}`);
}

// ── Offer → project phases/tasks import ───────────────────────────────────────
// An offer/tier has no phases of its own; it carries a flat list of services,
// and each service has a `fase` (catalog taxonomy). We materialize project
// phases by grouping the tier's services by `services.fase` (one phase per
// distinct fase, one task per service). Idempotent + mergeable: existing phases
// are matched by title (case-insensitive) and reused, duplicate tasks skipped —
// so assigning a 2nd offer with shared fasi adds tasks without duplicating.

const NO_FASE_LABEL = "Generale";

export async function importOfferIntoProject(projectId: string, microId: string): Promise<void> {
  await requireAdmin();

  // Tier services with their catalog fase, ordered (null fase last, then name).
  const rows = await db
    .select({ name: services.name, fase: services.fase })
    .from(offer_tier_services)
    .innerJoin(services, eq(offer_tier_services.service_id, services.id))
    .where(eq(offer_tier_services.tier_id, microId))
    .orderBy(asc(services.fase), asc(services.name));

  if (rows.length === 0) return;

  // Group service names by fase, preserving first-seen order.
  const groups: Array<{ fase: string; serviceNames: string[] }> = [];
  const groupIndex = new Map<string, number>();
  for (const r of rows) {
    const fase = r.fase?.trim() || NO_FASE_LABEL;
    let idx = groupIndex.get(fase.toLowerCase());
    if (idx === undefined) {
      idx = groups.length;
      groupIndex.set(fase.toLowerCase(), idx);
      groups.push({ fase, serviceNames: [] });
    }
    groups[idx].serviceNames.push(r.name);
  }

  // Existing project phases (for merge-by-title) + current max sort_order.
  const existingPhases = await db
    .select({ id: phases.id, title: phases.title, sort_order: phases.sort_order })
    .from(phases)
    .where(eq(phases.project_id, projectId));
  const phaseByTitle = new Map(existingPhases.map((p) => [p.title.trim().toLowerCase(), p]));
  let maxPhaseOrder = existingPhases.reduce((m, p) => Math.max(m, p.sort_order), -1);

  for (const group of groups) {
    let phaseId: string;
    const existing = phaseByTitle.get(group.fase.toLowerCase());
    if (existing) {
      phaseId = existing.id;
    } else {
      maxPhaseOrder += 1;
      const [inserted] = await db
        .insert(phases)
        .values({ project_id: projectId, title: group.fase, sort_order: maxPhaseOrder, status: "upcoming" })
        .returning({ id: phases.id });
      phaseId = inserted.id;
      phaseByTitle.set(group.fase.toLowerCase(), { id: phaseId, title: group.fase, sort_order: maxPhaseOrder });
    }

    // Skip tasks whose title already exists in this phase.
    const existingTasks = await db
      .select({ title: tasks.title, sort_order: tasks.sort_order })
      .from(tasks)
      .where(eq(tasks.phase_id, phaseId));
    const taskTitles = new Set(existingTasks.map((t) => t.title.trim().toLowerCase()));
    let maxTaskOrder = existingTasks.reduce((m, t) => Math.max(m, t.sort_order), -1);

    const toInsert = group.serviceNames
      .filter((name) => !taskTitles.has(name.trim().toLowerCase()))
      .map((name) => {
        taskTitles.add(name.trim().toLowerCase());
        maxTaskOrder += 1;
        return { phase_id: phaseId, title: name, status: "todo", sort_order: maxTaskOrder };
      });
    if (toInsert.length > 0) await db.insert(tasks).values(toInsert);
  }

  revalidatePath(`/admin/projects/${projectId}`);
}

// ── Offer assignment actions ──────────────────────────────────────────────────

const assignOfferSchema = z.object({
  project_id: z.string().min(1),
  micro_id: z.string().min(1, "Seleziona un'offerta"),
  accepted_total: z.coerce.number().min(0).optional(),
  import_phases: z.boolean().optional(),
});

export async function assignOfferToProject(formData: FormData) {
  await requireAdmin();
  const parsed = assignOfferSchema.safeParse({
    project_id: formData.get("project_id"),
    micro_id: formData.get("micro_id"),
    accepted_total: formData.get("accepted_total") || undefined,
    import_phases: formData.get("import_phases") === "on",
  });
  if (!parsed.success) throw new Error(parsed.error.issues[0].message);
  await db.insert(project_offers).values({
    project_id: parsed.data.project_id,
    micro_id: parsed.data.micro_id,
    accepted_total:
      parsed.data.accepted_total !== undefined
        ? parsed.data.accepted_total.toFixed(2)
        : null,
  });
  // Materialize project phases/tasks from the tier's services grouped by fase.
  if (parsed.data.import_phases) {
    await importOfferIntoProject(parsed.data.project_id, parsed.data.micro_id);
  }
  revalidatePath(`/admin/projects/${parsed.data.project_id}`);
}

export async function removeProjectOffer(projectOfferId: string, projectId: string) {
  await requireAdmin();
  await db.delete(project_offers).where(eq(project_offers.id, projectOfferId));
  revalidatePath(`/admin/projects/${projectId}`);
}

export async function updateProjectOfferTotal(
  projectOfferId: string,
  projectId: string,
  accepted_total: string
) {
  await requireAdmin();
  const amount = parseFloat(accepted_total);
  if (isNaN(amount) || amount < 0) throw new Error("Importo non valido");
  await db
    .update(project_offers)
    .set({ accepted_total: amount.toFixed(2) })
    .where(eq(project_offers.id, projectOfferId));
  revalidatePath(`/admin/projects/${projectId}`);
}