import prisma from "../config/database";
import {
  generateOfferNumber,
  previewOfferNumber,
  releaseOfferNumber,
  isOfferNumberTaken,
} from "./numbering.service";
import { nasOffersManager } from "./nas-offers-manager";

interface QuotationItemInput {
  description?: string;
  item_description?: string;
  quantity?: number;
  unit?: string;
  unit_price?: number;
  is_included_in_total?: boolean;
  position?: number;
}
interface ScopeSectionInput {
  title?: string;
  title_cz?: string;
  content?: string;
  position?: number;
}

// Re-export for convenience
export { previewOfferNumber as getNextOfferNumber } from "./numbering.service";

const ALLOWED_SORT_FIELDS = [
  "id",
  "quotation_number",
  "project_code",
  "created_at",
  "valid_until",
  "currency",
  "status",
];

interface ListOffersParams {
  page: number;
  limit: number;
  skip: number;
  sort: string;
  order: "asc" | "desc";
  search: string;
  status?: string;
  customer_id?: number;
}

function enrichQuotation(q: any) {
  const subtotal = q.quotation_items
    .filter((i: any) => i.is_included_in_total !== false)
    .reduce(
      (s: number, i: any) =>
        s + (Number(i.quantity) || 0) * (Number(i.unit_price) || 0),
      0,
    );
  const vatAmount = q.apply_vat
    ? subtotal *
      ((q.vat_rate != null && q.vat_rate !== "" ? Number(q.vat_rate) : 21) /
        100)
    : 0;
  const { quotation_items, scope_sections, ...rest } = q;
  return {
    ...rest,
    items: quotation_items,
    sections: scope_sections,
    customer_name: q.customers?.name || null,
    subtotal: Math.round(subtotal * 100) / 100,
    vat_amount: Math.round(vatAmount * 100) / 100,
    total: Math.round((subtotal + vatAmount) * 100) / 100,
  };
}

export async function listOffers(params: ListOffersParams) {
  const { page, limit, skip, sort, order, search, status, customer_id } =
    params;
  const sortField = ALLOWED_SORT_FIELDS.includes(sort) ? sort : "id";

  const where: Record<string, unknown> = {};
  if (status) where.status = status;
  if (customer_id) where.customer_id = customer_id;
  if (search) {
    where.OR = [
      { quotation_number: { contains: search } },
      { project_code: { contains: search } },
      { customers: { name: { contains: search } } },
    ];
  }

  const [quotations, total] = await Promise.all([
    prisma.quotations.findMany({
      where,
      skip,
      take: limit,
      orderBy: { [sortField]: order },
      include: {
        customers: { select: { id: true, name: true } },
        quotation_items: { orderBy: { position: "asc" } },
        scope_sections: { orderBy: { position: "asc" } },
      },
    }),
    prisma.quotations.count({ where }),
  ]);

  // Batch-fetch linked order statuses
  const orderIds = quotations
    .map((q) => q.order_id)
    .filter((id): id is number => id != null);
  const orderStatusMap = new Map<number, string>();
  if (orderIds.length > 0) {
    const orders = await prisma.orders.findMany({
      where: { id: { in: orderIds } },
      select: { id: true, status: true },
    });
    for (const o of orders) {
      orderStatusMap.set(o.id, o.status || "");
    }
  }

  const enriched = quotations.map((q) => ({
    ...enrichQuotation(q),
    order_status:
      q.order_id != null ? orderStatusMap.get(q.order_id) || null : null,
  }));

  return { data: enriched, total, page, limit };
}

export async function getOffer(id: number) {
  const quotation = await prisma.quotations.findUnique({
    where: { id },
    include: {
      customers: true,
      quotation_items: { orderBy: { position: "asc" } },
      scope_sections: { orderBy: { position: "asc" } },
    },
  });
  if (!quotation) return null;

  // Fetch linked order if exists
  let orderInfo = null;
  if (quotation.order_id) {
    const order = await prisma.orders.findUnique({
      where: { id: quotation.order_id },
      select: { id: true, order_number: true, status: true },
    });
    orderInfo = order;
  }

  const { quotation_items, scope_sections, ...rest } = quotation;
  return {
    ...rest,
    items: quotation_items,
    sections: scope_sections,
    customer: quotation.customers,
    customer_name: quotation.customers?.name || null,
    order: orderInfo,
  };
}

export async function createOffer(body: Record<string, any>) {
  try {
    return await prisma.$transaction(async (tx) => {
      const quotationNumber =
        body.quotation_number !== undefined && body.quotation_number !== null
          ? String(body.quotation_number)
          : await generateOfferNumber(tx);

      // Re-check uniqueness INSIDE the transaction to close the TOCTOU window
      // between a pre-transaction check and the insert (mirrors createOrder).
      // generateOfferNumber already verifies its own generated numbers, so this
      // guards the caller-supplied path.
      if (
        body.quotation_number !== undefined &&
        body.quotation_number !== null
      ) {
        const taken = await isOfferNumberTaken(quotationNumber);
        if (taken) {
          throw Object.assign(new Error("Číslo nabídky je již použito"), {
            status: 400,
          });
        }
      }

      const quotation = await tx.quotations.create({
        data: {
          quotation_number: quotationNumber,
          project_code: body.project_code ? String(body.project_code) : null,
          customer_id: body.customer_id ? Number(body.customer_id) : null,
          created_at: body.created_at
            ? new Date(String(body.created_at))
            : undefined,
          valid_until: body.valid_until
            ? new Date(String(body.valid_until))
            : null,
          currency: body.currency ? String(body.currency) : "CZK",
          language: body.language ? String(body.language) : "cs",
          vat_rate: body.vat_rate != null ? Number(body.vat_rate) : 21.0,
          apply_vat: body.apply_vat !== false,
          status: body.status ? String(body.status) : "active",
          scope_title: body.scope_title ? String(body.scope_title) : null,
          scope_description: body.scope_description
            ? String(body.scope_description)
            : null,
        },
      });

      if (Array.isArray(body.items)) {
        await tx.quotation_items.createMany({
          data: (body.items as QuotationItemInput[]).map((item, i) => ({
            quotation_id: quotation.id,
            description: item.description ?? null,
            item_description: item.item_description ?? null,
            quantity: item.quantity ?? 1,
            unit: item.unit ?? null,
            unit_price: item.unit_price ?? 0,
            is_included_in_total: item.is_included_in_total !== false,
            position: item.position ?? i,
          })),
        });
      }

      if (Array.isArray(body.sections)) {
        await tx.scope_sections.createMany({
          data: (body.sections as ScopeSectionInput[]).map((s, i) => ({
            quotation_id: quotation.id,
            title: s.title ?? null,
            title_cz: s.title_cz ?? null,
            content: s.content ?? null,
            position: s.position ?? i,
          })),
        });
      }

      return quotation;
    });
  } catch (err) {
    if (err instanceof Error && "status" in err) {
      return {
        error: err.message,
        status: (err as Error & { status: number }).status,
      } as const;
    }
    throw err;
  }
}

export async function updateOffer(id: number, body: Record<string, any>) {
  const existing = await prisma.quotations.findUnique({ where: { id } });
  if (!existing) return { error: "not_found" as const };
  if (existing.status === "invalidated")
    return { error: "invalidated" as const };

  if (
    body.quotation_number !== undefined &&
    String(body.quotation_number) !== existing.quotation_number
  ) {
    return { error: "Číslo nabídky nelze změnit", status: 400 } as const;
  }

  const data = {
    customer_id:
      body.customer_id !== undefined ? Number(body.customer_id) : undefined,
    created_at:
      body.created_at !== undefined
        ? body.created_at
          ? new Date(String(body.created_at))
          : null
        : undefined,
    valid_until:
      body.valid_until !== undefined
        ? body.valid_until
          ? new Date(String(body.valid_until))
          : null
        : undefined,
    currency: body.currency !== undefined ? String(body.currency) : undefined,
    language: body.language !== undefined ? String(body.language) : undefined,
    vat_rate: body.vat_rate !== undefined ? Number(body.vat_rate) : undefined,
    apply_vat:
      body.apply_vat !== undefined
        ? body.apply_vat === true ||
          body.apply_vat === 1 ||
          body.apply_vat === "1"
        : undefined,
    status: body.status !== undefined ? String(body.status) : undefined,
    project_code:
      body.project_code !== undefined
        ? body.project_code
          ? String(body.project_code)
          : null
        : undefined,
    scope_title:
      body.scope_title !== undefined
        ? body.scope_title
          ? String(body.scope_title)
          : null
        : undefined,
    scope_description:
      body.scope_description !== undefined
        ? body.scope_description
          ? String(body.scope_description)
          : null
        : undefined,
    modified_at: new Date(),
  };

  if (Array.isArray(body.items) || Array.isArray(body.sections)) {
    await prisma.$transaction(async (tx) => {
      await tx.quotations.update({ where: { id }, data });
      if (Array.isArray(body.items)) {
        await tx.quotation_items.deleteMany({ where: { quotation_id: id } });
        await tx.quotation_items.createMany({
          data: (body.items as QuotationItemInput[]).map((item, i) => ({
            quotation_id: id,
            description: item.description ?? null,
            item_description: item.item_description ?? null,
            quantity: item.quantity ?? 1,
            unit: item.unit ?? null,
            unit_price: item.unit_price ?? 0,
            is_included_in_total: item.is_included_in_total !== false,
            position: item.position ?? i,
          })),
        });
      }
      if (Array.isArray(body.sections)) {
        await tx.scope_sections.deleteMany({ where: { quotation_id: id } });
        await tx.scope_sections.createMany({
          data: (body.sections as ScopeSectionInput[]).map((s, i) => ({
            quotation_id: id,
            title: s.title ?? null,
            title_cz: s.title_cz ?? null,
            content: s.content ?? null,
            position: s.position ?? i,
          })),
        });
      }
    });
  } else {
    await prisma.quotations.update({ where: { id }, data });
  }

  return { id, quotation_number: existing.quotation_number };
}

export async function deleteOffer(id: number) {
  const existing = await prisma.quotations.findUnique({ where: { id } });
  if (!existing) return null;

  await prisma.quotations.delete({ where: { id } });

  const year = existing.created_at
    ? new Date(existing.created_at).getFullYear()
    : new Date().getFullYear();
  await releaseOfferNumber(year, existing.quotation_number ?? undefined);

  // Remove the offer PDF from the NAS so the DB delete doesn't orphan it.
  // Best-effort and idempotent: deleteOfferPdf returns false (and logs) if the
  // file is already gone, so a NAS outage / missing file never throws here.
  if (existing.quotation_number && nasOffersManager.isConfigured()) {
    try {
      const relPath = nasOffersManager.buildRelativePath(
        existing.quotation_number,
        year,
      );
      nasOffersManager.deleteOfferPdf(relPath);
    } catch (e) {
      console.error(
        "[offers.service] NAS offer PDF delete failed for",
        existing.quotation_number,
        e,
      );
    }
  }

  return existing;
}

export async function duplicateOffer(id: number) {
  const original = await prisma.quotations.findUnique({
    where: { id },
    include: {
      quotation_items: { orderBy: { position: "asc" } },
      scope_sections: { orderBy: { position: "asc" } },
    },
  });
  if (!original) return null;

  return prisma.$transaction(async (tx) => {
    const nextOfferNumber = await generateOfferNumber(tx);

    const copy = await tx.quotations.create({
      data: {
        quotation_number: nextOfferNumber,
        project_code: original.project_code,
        customer_id: original.customer_id,
        valid_until: null,
        currency: original.currency,
        language: original.language,
        vat_rate: original.vat_rate,
        apply_vat: original.apply_vat,
        status: "active",
        scope_title: original.scope_title,
        scope_description: original.scope_description,
      },
    });

    if (original.quotation_items.length > 0) {
      await tx.quotation_items.createMany({
        data: original.quotation_items.map((item) => ({
          quotation_id: copy.id,
          description: item.description,
          item_description: item.item_description,
          quantity: item.quantity,
          unit: item.unit,
          unit_price: item.unit_price,
          is_included_in_total: item.is_included_in_total,
          position: item.position,
        })),
      });
    }

    if (original.scope_sections.length > 0) {
      await tx.scope_sections.createMany({
        data: original.scope_sections.map((s) => ({
          quotation_id: copy.id,
          title: s.title,
          title_cz: s.title_cz,
          content: s.content,
          position: s.position,
        })),
      });
    }

    return { copy, original };
  });
}

export async function invalidateOffer(id: number) {
  const existing = await prisma.quotations.findUnique({ where: { id } });
  if (!existing) return null;

  await prisma.quotations.update({
    where: { id },
    data: { status: "invalidated", modified_at: new Date() },
  });
  return existing;
}
