import { db } from "@/db";
import {
  offer_macros,
  offer_micros,
  offer_services,
  offer_micro_services,
  offer_tier_services,
  services,
  tags,
} from "@/db/schema";
import { getPool } from "@/lib/taxonomy";
import type { OfferMacro, OfferMicro, OfferService } from "@/db/schema";
import { eq, and, asc, inArray, sql } from "drizzle-orm";

export type MicroWithServices = OfferMicro & {
  services: Array<{ id: string; name: string; price: string }>;
  cumulative_price: string;
};

export type MacroWithMicros = OfferMacro & {
  micros: MicroWithServices[];
};

export async function getCatalogWithMicros(): Promise<MacroWithMicros[]> {
  const macros = await db
    .select()
    .from(offer_macros)
    .orderBy(asc(offer_macros.sort_order), asc(offer_macros.created_at));

  if (macros.length === 0) return [];

  const macroIds = macros.map((m) => m.id);

  const micros = await db
    .select()
    .from(offer_micros)
    .where(inArray(offer_micros.macro_id, macroIds))
    .orderBy(asc(offer_micros.sort_order));

  const microIds = micros.map((m) => m.id);

  if (microIds.length === 0) {
    return macros.map((m) => ({ ...m, micros: [] }));
  }

  // Fetch junction rows + service data in one query
  const assignments = await db
    .select({
      micro_id: offer_micro_services.micro_id,
      service_id: offer_services.id,
      name: offer_services.name,
      price: offer_services.price,
    })
    .from(offer_micro_services)
    .innerJoin(offer_services, eq(offer_micro_services.service_id, offer_services.id))
    .where(inArray(offer_micro_services.micro_id, microIds));

  // Cumulative price per micro
  const cumulRows = await db
    .select({
      micro_id: offer_micro_services.micro_id,
      cumulative_price: sql<string>`coalesce(sum(${offer_services.price}::numeric), 0)`,
    })
    .from(offer_micro_services)
    .innerJoin(offer_services, eq(offer_micro_services.service_id, offer_services.id))
    .where(inArray(offer_micro_services.micro_id, microIds))
    .groupBy(offer_micro_services.micro_id);

  const cumulMap = new Map(cumulRows.map((r) => [r.micro_id, r.cumulative_price]));

  const microsWithServices: MicroWithServices[] = micros.map((micro) => ({
    ...micro,
    services: assignments
      .filter((a) => a.micro_id === micro.id)
      .map((a) => ({ id: a.service_id, name: a.name, price: String(a.price) })),
    cumulative_price: cumulMap.get(micro.id) ?? "0",
  }));

  return macros.map((macro) => ({
    ...macro,
    micros: microsWithServices.filter((m) => m.macro_id === macro.id),
  }));
}

export async function getAllOfferServices(): Promise<OfferService[]> {
  return db
    .select()
    .from(offer_services)
    .where(eq(offer_services.active, true))
    .orderBy(asc(offer_services.name));
}

// Returns assigned service IDs for a given micro-offer (used by ServiceCheckboxList)
export async function getMicroAssignedServiceIds(microId: string): Promise<string[]> {
  const rows = await db
    .select({ service_id: offer_micro_services.service_id })
    .from(offer_micro_services)
    .where(eq(offer_micro_services.micro_id, microId));
  return rows.map((r) => r.service_id);
}

// ── Phase 12: Offer Editor query layer ──────────────────────────────────────
// 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";

export type OfferListTier = {
  tier_letter: string | null;
  public_name: string;
  public_price: string | null;
  servicesTotal: string;
};

export type OfferListCard = Pick<
  OfferMacro,
  "id" | "internal_name" | "description" | "category" | "is_archived"
> & { tiers: OfferListTier[] };

// List-page cards (Plan 04): one row per offer_macros, with category + archive
// status for client-side filtering. Archived offers ARE included — the
// "Mostra offerte archiviate" toggle filters client-side per UI-SPEC.
export async function getOfferListCards(): Promise<OfferListCard[]> {
  const macros = await db
    .select({
      id: offer_macros.id,
      internal_name: offer_macros.internal_name,
      description: offer_macros.description,
      category: offer_macros.category,
      is_archived: offer_macros.is_archived,
    })
    .from(offer_macros)
    .orderBy(asc(offer_macros.sort_order), asc(offer_macros.created_at));

  if (macros.length === 0) return [];

  const macroIds = macros.map((m) => m.id);

  // Fetch all tiers for these macros, ordered A -> B -> C
  const tierOrder = sql`CASE ${offer_micros.tier_letter} WHEN 'A' THEN 1 WHEN 'B' THEN 2 WHEN 'C' THEN 3 ELSE 4 END`;
  const tierRows = await db
    .select({
      id: offer_micros.id,
      macro_id: offer_micros.macro_id,
      tier_letter: offer_micros.tier_letter,
      public_name: offer_micros.public_name,
      public_price: offer_micros.public_price,
    })
    .from(offer_micros)
    .where(inArray(offer_micros.macro_id, macroIds))
    .orderBy(asc(tierOrder));

  const allTierIds = tierRows.map((t) => t.id);

  // Compute servicesTotal per tier in batch
  let totalsByTierId = new Map<string, string>();
  if (allTierIds.length > 0) {
    const totalRows = await db
      .select({
        tier_id: offer_tier_services.tier_id,
        servicesTotal: sql<string>`coalesce(sum(${services.unit_price}::numeric), 0)`,
      })
      .from(offer_tier_services)
      .innerJoin(services, eq(offer_tier_services.service_id, services.id))
      .where(inArray(offer_tier_services.tier_id, allTierIds))
      .groupBy(offer_tier_services.tier_id);

    totalsByTierId = new Map(totalRows.map((r) => [r.tier_id, r.servicesTotal]));
  }

  // Group tiers under each macro
  const tiersByMacroId = new Map<string, OfferListTier[]>();
  for (const t of tierRows) {
    const list = tiersByMacroId.get(t.macro_id) ?? [];
    list.push({
      tier_letter: t.tier_letter,
      public_name: t.public_name,
      public_price: t.public_price,
      servicesTotal: totalsByTierId.get(t.id) ?? "0",
    });
    tiersByMacroId.set(t.macro_id, list);
  }

  return macros.map((m) => ({ ...m, tiers: tiersByMacroId.get(m.id) ?? [] }));
}

export type OfferTierData = {
  id: string;
  tier_letter: string | null;
  internal_name: string;
  public_name: string;
  duration_months: number;
  public_price: string | null;
  assignedServiceIds: string[];
  servicesTotal: string;
};

export type OfferEditorData = {
  macro: OfferMacro;
  tiers: OfferTierData[];
  availableServices: Array<{
    id: string;
    name: string;
    unit_price: string;
    category: string | null;
    offerTags: string[];
  }>;
  tipoTags: string[];
  obiettivoTags: string[];
};

// Full editor data for a single offer (Plan 05): macro fields (incl.
// category/ticket/is_archived/transformation-promise), its A/B/C tiers with
// assigned service IDs + computed services total, the category-filtered
// service catalog for the composition matrix, and Tipo/Obiettivo tags.
export async function getOfferEditorData(macroId: string): Promise<OfferEditorData | null> {
  const [macro] = await db.select().from(offer_macros).where(eq(offer_macros.id, macroId));
  if (!macro) return null;

  // Tiers ordered A -> B -> C, with null/unrecognized tier_letter sorted last.
  const tierOrder = sql`CASE ${offer_micros.tier_letter} WHEN 'A' THEN 1 WHEN 'B' THEN 2 WHEN 'C' THEN 3 ELSE 4 END`;
  const tiers = await db
    .select()
    .from(offer_micros)
    .where(eq(offer_micros.macro_id, macroId))
    .orderBy(asc(tierOrder));

  const tierIds = tiers.map((t) => t.id);

  let assignedRows: Array<{ tier_id: string; service_id: string }> = [];
  let totalsMap = new Map<string, string>();

  if (tierIds.length > 0) {
    assignedRows = await db
      .select({
        tier_id: offer_tier_services.tier_id,
        service_id: offer_tier_services.service_id,
      })
      .from(offer_tier_services)
      .where(inArray(offer_tier_services.tier_id, tierIds));

    const totalRows = await db
      .select({
        tier_id: offer_tier_services.tier_id,
        servicesTotal: sql<string>`coalesce(sum(${services.unit_price}::numeric), 0)`,
      })
      .from(offer_tier_services)
      .innerJoin(services, eq(offer_tier_services.service_id, services.id))
      .where(inArray(offer_tier_services.tier_id, tierIds))
      .groupBy(offer_tier_services.tier_id);

    totalsMap = new Map(totalRows.map((r) => [r.tier_id, r.servicesTotal]));
  }

  const tiersData: OfferTierData[] = tiers.map((tier) => ({
    id: tier.id,
    tier_letter: tier.tier_letter,
    internal_name: tier.internal_name,
    public_name: tier.public_name,
    duration_months: tier.duration_months,
    public_price: tier.public_price,
    assignedServiceIds: assignedRows
      .filter((a) => a.tier_id === tier.id)
      .map((a) => a.service_id),
    servicesTotal: totalsMap.get(tier.id) ?? "0",
  }));

  // Full service catalog with offer membership tags (entity_type = "services").
  const allServices = await db
    .select({
      id: services.id,
      name: services.name,
      unit_price: services.unit_price,
      category: services.category,
    })
    .from(services)
    .where(eq(services.active, true));

  const serviceIds = allServices.map((s) => s.id);
  const offerTagRows =
    serviceIds.length > 0
      ? await db
          .select({ entity_id: tags.entity_id, name: tags.name })
          .from(tags)
          .where(and(eq(tags.entity_type, "services"), inArray(tags.entity_id, serviceIds)))
      : [];

  const tagsByServiceId = new Map<string, string[]>();
  for (const row of offerTagRows) {
    const list = tagsByServiceId.get(row.entity_id) ?? [];
    list.push(row.name);
    tagsByServiceId.set(row.entity_id, list);
  }

  const availableServices = allServices.map((s) => ({
    ...s,
    offerTags: tagsByServiceId.get(s.id) ?? [],
  }));

  // Tipo/Obiettivo tags for this macro.
  const tagRows = await db
    .select({ name: tags.name, type: tags.entity_type })
    .from(tags)
    .where(
      and(
        eq(tags.entity_id, macroId),
        inArray(tags.entity_type, [OFFER_TIPO_ENTITY, OFFER_OBIETTIVO_ENTITY])
      )
    );

  const tipoTags = tagRows.filter((r) => r.type === OFFER_TIPO_ENTITY).map((r) => r.name);
  const obiettivoTags = tagRows
    .filter((r) => r.type === OFFER_OBIETTIVO_ENTITY)
    .map((r) => r.name);

  return {
    macro,
    tiers: tiersData,
    availableServices,
    tipoTags,
    obiettivoTags,
  };
}

export type OfferFieldOptions = {
  categoria: string[];
  ticket: string[];
  tipo: string[];
  obiettivo: string[];
};

// Shared option pools for the offer editor's select fields (Notion-style
// dropdowns). All four read from the centralized, persistent taxonomy pools
// (src/lib/taxonomy.ts), seeded from existing values on first access.
export async function getOfferFieldOptions(): Promise<OfferFieldOptions> {
  const [categoria, ticket, tipo, obiettivo] = await Promise.all([
    getPool("offer_categoria"),
    getPool("offer_ticket"),
    getPool("offer_tipo"),
    getPool("offer_obiettivo"),
  ]);
  return { categoria, ticket, tipo, obiettivo };
}
