"use server";

import { db } from "@/db";
import {
  offer_macros,
  offer_micros,
  offer_services,
  offer_micro_services,
  offer_tier_services,
  tags,
} from "@/db/schema";
import { revalidatePath } from "next/cache";
import { eq, and, inArray } from "drizzle-orm";
import { z } from "zod";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { addPoolValue, renamePoolValue } from "@/lib/taxonomy";

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

// ── Macro-offer CRUD ─────────────────────────────────────────────────────────

const macroSchema = z.object({
  internal_name: z.string().min(1, "Nome interno richiesto"),
  public_name: z.string().min(1, "Nome pubblico richiesto"),
  transformation_promise: z.string().optional(),
});

export async function createMacro(formData: FormData) {
  await requireAdmin();
  const parsed = macroSchema.safeParse({
    internal_name: formData.get("internal_name"),
    public_name: formData.get("public_name"),
    transformation_promise: formData.get("transformation_promise") ?? "",
  });
  if (!parsed.success) throw new Error(parsed.error.issues[0].message);
  await db.insert(offer_macros).values({
    internal_name: parsed.data.internal_name,
    public_name: parsed.data.public_name,
    transformation_promise: parsed.data.transformation_promise || null,
  });
  revalidatePath("/admin/offers");
}

export async function deleteMacro(macroId: string) {
  await requireAdmin();
  await db.delete(offer_macros).where(eq(offer_macros.id, macroId));
  revalidatePath("/admin/offers");
}

// ── Micro-offer CRUD ─────────────────────────────────────────────────────────

const microSchema = z.object({
  macro_id: z.string().min(1, "Macro offerta richiesta"),
  internal_name: z.string().min(1, "Nome interno richiesto"),
  public_name: z.string().min(1, "Nome pubblico richiesto"),
  transformation_promise: z.string().optional(),
  duration_months: z.coerce.number().int().min(1, "Durata minima 1 mese"),
});

export async function createMicro(formData: FormData) {
  await requireAdmin();
  const parsed = microSchema.safeParse({
    macro_id: formData.get("macro_id"),
    internal_name: formData.get("internal_name"),
    public_name: formData.get("public_name"),
    transformation_promise: formData.get("transformation_promise") ?? "",
    duration_months: formData.get("duration_months"),
  });
  if (!parsed.success) throw new Error(parsed.error.issues[0].message);
  await db.insert(offer_micros).values({
    macro_id: parsed.data.macro_id,
    internal_name: parsed.data.internal_name,
    public_name: parsed.data.public_name,
    transformation_promise: parsed.data.transformation_promise || null,
    duration_months: parsed.data.duration_months,
  });
  revalidatePath("/admin/offers");
}

export async function deleteMicro(microId: string) {
  await requireAdmin();
  // offer_micro_services will cascade; project_offers will restrict (DB constraint)
  await db.delete(offer_micros).where(eq(offer_micros.id, microId));
  revalidatePath("/admin/offers");
}

// ── Offer service CRUD ───────────────────────────────────────────────────────

const offerServiceSchema = z.object({
  name: z.string().min(1, "Nome richiesto"),
  price: z.coerce.number().min(0, "Prezzo non può essere negativo"),
  transformation_description: z.string().optional(),
});

export async function createOfferService(formData: FormData) {
  await requireAdmin();
  const parsed = offerServiceSchema.safeParse({
    name: formData.get("name"),
    price: formData.get("price"),
    transformation_description: formData.get("transformation_description") ?? "",
  });
  if (!parsed.success) throw new Error(parsed.error.issues[0].message);
  await db.insert(offer_services).values({
    name: parsed.data.name,
    price: parsed.data.price.toFixed(2),
    transformation_description: parsed.data.transformation_description || null,
  });
  revalidatePath("/admin/offers");
}

export async function toggleOfferServiceActive(serviceId: string, active: boolean) {
  await requireAdmin();
  await db.update(offer_services).set({ active }).where(eq(offer_services.id, serviceId));
  revalidatePath("/admin/offers");
}

// ── Multi-select service assignment ─────────────────────────────────────────

// Replaces all assignments for a micro with the provided serviceIds (upsert-replace pattern)
export async function updateMicroOfferServices(microId: string, serviceIds: string[]) {
  await requireAdmin();
  // Delete existing assignments for this micro
  await db.delete(offer_micro_services).where(eq(offer_micro_services.micro_id, microId));
  // Re-insert with new set (empty array = unassign all)
  if (serviceIds.length > 0) {
    await db.insert(offer_micro_services).values(
      serviceIds.map((serviceId) => ({ micro_id: microId, service_id: serviceId }))
    );
  }
  revalidatePath("/admin/offers");
}

// ── Phase 12: Offer Editor server actions ───────────────────────────────────
// entity_type values for the polymorphic `tags` table, scoped to offer_macros'
// Tipo/Obiettivo dimensions (D-06 pattern — separate pools from "services"/"leads").
const OFFER_TIPO_ENTITY = "offer_macros.tipo";
const OFFER_OBIETTIVO_ENTITY = "offer_macros.obiettivo";

const tierSchema = z.object({
  id: z.string().optional(),
  tier_letter: z.enum(["A", "B", "C"]),
  internal_name: z.string().min(1),
  public_name: z.string().min(1),
  duration_months: z.coerce.number().int().min(1),
  public_price: z.coerce.number().min(0).optional().nullable(),
  assignedServiceIds: z.array(z.string()),
});

const saveOfferEditorSchema = z.object({
  internal_name: z.string().min(1, "Nome interno richiesto"),
  public_name: z.string().min(1, "Nome pubblico richiesto"),
  description: z.string().optional(),
  category: z.string().optional(),
  ticket: z.string().optional(),
  offer_type: z.enum(["una_tantum", "retainer"]).optional(),
  cliente_ideale: z.string().optional(),
  risultato: z.string().optional(),
  tempo: z.string().optional(),
  pain: z.string().optional(),
  metodo: z.string().optional(),
  tiers: z.array(tierSchema).max(3),
  tipoTags: z.array(z.string()),
  obiettivoTags: z.array(z.string()),
});

export type SaveOfferEditorPayload = z.infer<typeof saveOfferEditorSchema>;

// Persists the entire offer editor state in one call: macro scalar fields
// (incl. category/ticket/transformation-promise), per-tier upsert
// (tier_letter validated A/B/C via Zod — defense-in-depth alongside the DB
// CHECK constraint from Plan 01) with delete-then-reinsert
// offer_tier_services, and Tipo/Obiettivo tags (delete-then-reinsert).
export async function saveOfferEditor(macroId: string, payload: SaveOfferEditorPayload) {
  await requireAdmin();

  const parsed = saveOfferEditorSchema.safeParse(payload);
  if (!parsed.success) throw new Error(parsed.error.issues[0].message);
  const data = parsed.data;

  // 1. Update macro scalar fields.
  await db
    .update(offer_macros)
    .set({
      internal_name: data.internal_name,
      public_name: data.public_name,
      description: data.description || null,
      category: data.category || null,
      ticket: data.ticket || null,
      offer_type: data.offer_type ?? "una_tantum",
      cliente_ideale: data.cliente_ideale || null,
      risultato: data.risultato || null,
      tempo: data.tempo || null,
      pain: data.pain || null,
      metodo: data.metodo || null,
    })
    .where(eq(offer_macros.id, macroId));

  // 2. Upsert each tier, then replace its offer_tier_services assignments.
  for (const tier of data.tiers) {
    const publicPrice = tier.public_price != null ? String(tier.public_price) : null;
    let tierId: string;

    if (tier.id) {
      await db
        .update(offer_micros)
        .set({
          tier_letter: tier.tier_letter,
          internal_name: tier.internal_name,
          public_name: tier.public_name,
          duration_months: tier.duration_months,
          public_price: publicPrice,
        })
        .where(eq(offer_micros.id, tier.id));
      tierId = tier.id;
    } else {
      const [inserted] = await db
        .insert(offer_micros)
        .values({
          macro_id: macroId,
          tier_letter: tier.tier_letter,
          internal_name: tier.internal_name,
          public_name: tier.public_name,
          duration_months: tier.duration_months,
          public_price: publicPrice,
        })
        .returning({ id: offer_micros.id });
      tierId = inserted.id;
    }

    // Delete-then-reinsert offer_tier_services (mirrors updateMicroOfferServices).
    await db.delete(offer_tier_services).where(eq(offer_tier_services.tier_id, tierId));
    if (tier.assignedServiceIds.length > 0) {
      await db.insert(offer_tier_services).values(
        tier.assignedServiceIds.map((serviceId) => ({ tier_id: tierId, service_id: serviceId }))
      );
    }
  }

  // 3. Delete-then-reinsert Tipo/Obiettivo tags.
  await db
    .delete(tags)
    .where(
      and(
        eq(tags.entity_id, macroId),
        inArray(tags.entity_type, [OFFER_TIPO_ENTITY, OFFER_OBIETTIVO_ENTITY])
      )
    );

  const tagRows = [
    ...data.tipoTags.map((name) => ({ entity_type: OFFER_TIPO_ENTITY, entity_id: macroId, name })),
    ...data.obiettivoTags.map((name) => ({
      entity_type: OFFER_OBIETTIVO_ENTITY,
      entity_id: macroId,
      name,
    })),
  ];
  if (tagRows.length > 0) {
    await db.insert(tags).values(tagRows).onConflictDoNothing();
  }

  // 4. Register any newly-typed values into the persistent taxonomy pools so they
  // survive unassignment and appear in settings + every dropdown.
  if (data.category) await addPoolValue("offer_categoria", data.category);
  if (data.ticket) await addPoolValue("offer_ticket", data.ticket);
  for (const name of data.tipoTags) await addPoolValue("offer_tipo", name);
  for (const name of data.obiettivoTags) await addPoolValue("offer_obiettivo", name);

  revalidatePath("/admin/offers");
  revalidatePath(`/admin/offers/${macroId}/edit`);
}

// Toggles the archive flag on an offer macro (OFFER-18).
export async function toggleOfferArchived(macroId: string, archived: boolean) {
  await requireAdmin();
  await db.update(offer_macros).set({ is_archived: archived }).where(eq(offer_macros.id, macroId));
  revalidatePath("/admin/offers");
}

// ── Tipo/Obiettivo tag CRUD (mirrors addServiceOption/removeServiceOption) ───

type OfferTagDimension = "tipo" | "obiettivo";

const OFFER_TAG_ENTITY: Record<OfferTagDimension, string> = {
  tipo: OFFER_TIPO_ENTITY,
  obiettivo: OFFER_OBIETTIVO_ENTITY,
};

export async function addOfferTag(dimension: OfferTagDimension, macroId: string, value: string) {
  await requireAdmin();
  if (!(dimension in OFFER_TAG_ENTITY)) throw new Error(`Dimensione non valida: ${dimension}`);
  const trimmed = value.trim();
  if (trimmed.length === 0) throw new Error("Valore richiesto");

  await db
    .insert(tags)
    .values({ entity_type: OFFER_TAG_ENTITY[dimension], entity_id: macroId, name: trimmed })
    .onConflictDoNothing();

  await addPoolValue(dimension === "tipo" ? "offer_tipo" : "offer_obiettivo", trimmed);

  revalidatePath("/admin/offers");
}

export async function removeOfferTag(
  dimension: OfferTagDimension,
  macroId: string,
  value: string
) {
  await requireAdmin();
  if (!(dimension in OFFER_TAG_ENTITY)) throw new Error(`Dimensione non valida: ${dimension}`);

  await db
    .delete(tags)
    .where(
      and(
        eq(tags.entity_type, OFFER_TAG_ENTITY[dimension]),
        eq(tags.entity_id, macroId),
        eq(tags.name, value)
      )
    );

  revalidatePath("/admin/offers");
}

// ── Rename an option everywhere it is used (mirrors renameServiceOption) ────
// "categoria"/"ticket" are single-select columns on offer_macros; "tipo"/
// "obiettivo" are multi-select pools in the polymorphic `tags` table.

type OfferOptionField = "categoria" | "ticket" | "tipo" | "obiettivo";

export async function renameOfferOption(
  field: OfferOptionField,
  oldValue: string,
  newValue: string
) {
  await requireAdmin();
  const next = newValue.trim();
  if (next.length === 0) throw new Error("Nuovo nome richiesto");
  if (next === oldValue) return;

  if (field === "categoria") {
    await db.update(offer_macros).set({ category: next }).where(eq(offer_macros.category, oldValue));
    await renamePoolValue("offer_categoria", oldValue, next);
  } else if (field === "ticket") {
    await db.update(offer_macros).set({ ticket: next }).where(eq(offer_macros.ticket, oldValue));
    await renamePoolValue("offer_ticket", oldValue, next);
  } else if (field === "tipo" || field === "obiettivo") {
    await db
      .update(tags)
      .set({ name: next })
      .where(and(eq(tags.entity_type, OFFER_TAG_ENTITY[field]), eq(tags.name, oldValue)));
    await renamePoolValue(field === "tipo" ? "offer_tipo" : "offer_obiettivo", oldValue, next);
  } else {
    throw new Error(`Campo non valido: ${field}`);
  }

  revalidatePath("/admin/offers");
}

// ── Create a new offer macro (Plan 04 "+ Nuova Offerta" target) ─────────────

const createOfferMacroSchema = z.object({
  internal_name: z.string().min(1, "Nome interno richiesto"),
  public_name: z.string().optional(),
  description: z.string().optional(),
  category: z.string().optional(),
});

export async function createOfferMacro(formData: FormData) {
  await requireAdmin();
  const parsed = createOfferMacroSchema.safeParse({
    internal_name: formData.get("internal_name"),
    public_name: formData.get("public_name") ?? "",
    description: formData.get("description") ?? "",
    category: formData.get("category") ?? "",
  });
  if (!parsed.success) throw new Error(parsed.error.issues[0].message);

  await db.insert(offer_macros).values({
    internal_name: parsed.data.internal_name,
    public_name: parsed.data.public_name?.trim() || parsed.data.internal_name,
    description: parsed.data.description || null,
    category: parsed.data.category || null,
  });
  if (parsed.data.category) await addPoolValue("offer_categoria", parsed.data.category);

  revalidatePath("/admin/offers");
}
