import type { $Enums } from "@prisma/client";
import prisma from "../config/database";
import {
  generateIssuedOrderNumber,
  previewIssuedOrderNumber,
  releaseIssuedOrderNumber,
} from "./numbering.service";

export interface IssuedOrderItemInput {
  description?: string | null;
  item_description?: string | null;
  quantity?: number | string | null;
  unit?: string | null;
  unit_price?: number | string | null;
  vat_rate?: number | string | null;
  position?: number | null;
}

export interface IssuedOrderInput {
  po_number?: string | number | null;
  customer_id?: number | string | null;
  status?: string;
  currency?: string;
  vat_rate?: number | string | null;
  apply_vat?: boolean | number | string;
  exchange_rate?: number | string | null;
  order_date?: string | null;
  delivery_date?: string | null;
  language?: string;
  delivery_terms?: string | null;
  payment_terms?: string | null;
  issued_by?: string | null;
  notes?: string | null;
  internal_notes?: string | null;
  items?: IssuedOrderItemInput[];
  [key: string]: unknown;
}

interface ListIssuedOrdersParams {
  page: number;
  limit: number;
  skip: number;
  sort: string;
  order: string;
  search?: string;
  status?: string;
  customer_id?: number;
}

const VALID_TRANSITIONS: Record<string, string[]> = {
  draft: ["sent", "cancelled"],
  sent: ["confirmed", "cancelled"],
  confirmed: ["completed", "cancelled"],
  completed: [],
  cancelled: [],
};

const ALLOWED_SORT_FIELDS = [
  "id",
  "po_number",
  "status",
  "order_date",
  "currency",
];

/** NET base + VAT-on-top, rounded per line before accumulation (mirrors invoices). */
export function computeIssuedOrderTotals(
  items: Array<{ quantity: unknown; unit_price: unknown; vat_rate: unknown }>,
  applyVat: boolean | null,
  defaultVatRate: unknown,
) {
  let subtotal = 0;
  let vat = 0;
  for (const it of items) {
    const base = (Number(it.quantity) || 0) * (Number(it.unit_price) || 0);
    subtotal += base;
    if (applyVat) {
      const rate =
        it.vat_rate != null && it.vat_rate !== ""
          ? Number(it.vat_rate)
          : defaultVatRate != null
            ? Number(defaultVatRate)
            : 21;
      vat += Math.round(base * (rate / 100) * 100) / 100;
    }
  }
  return {
    subtotal: Math.round(subtotal * 100) / 100,
    vat_amount: Math.round(vat * 100) / 100,
    total: Math.round((subtotal + vat) * 100) / 100,
  };
}

export async function listIssuedOrders(params: ListIssuedOrdersParams) {
  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 = [
      { po_number: { contains: search } },
      { customers: { name: { contains: search } } },
      { customers: { company_id: { contains: search } } },
    ];
  }

  // Normalize the direction so an unexpected value can't reach Prisma at runtime.
  const dir: "asc" | "desc" = order === "asc" ? "asc" : "desc";
  // Tiebreak on id so same-day order_date rows sort deterministically.
  const orderBy: Record<string, "asc" | "desc">[] = [
    { [sortField]: dir },
    { id: dir },
  ];

  const [rows, total] = await Promise.all([
    prisma.issued_orders.findMany({
      where,
      skip,
      take: limit,
      orderBy,
      include: {
        customers: { select: { id: true, name: true } },
        issued_order_items: true,
      },
    }),
    prisma.issued_orders.count({ where }),
  ]);

  const enriched = rows.map((o) => {
    const totals = computeIssuedOrderTotals(
      o.issued_order_items,
      o.apply_vat,
      o.vat_rate,
    );
    const { issued_order_items, ...rest } = o;
    return {
      ...rest,
      items: issued_order_items,
      customer_name: o.customers?.name || null,
      ...totals,
    };
  });

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

export async function getIssuedOrder(id: number) {
  const order = await prisma.issued_orders.findUnique({
    where: { id },
    include: {
      customers: true,
      issued_order_items: { orderBy: { position: "asc" } },
    },
  });
  if (!order) return null;
  const { issued_order_items, ...rest } = order;
  return {
    ...rest,
    items: issued_order_items,
    customer: order.customers,
    customer_name: order.customers?.name || null,
    valid_transitions: VALID_TRANSITIONS[order.status as string] || [],
  };
}

export async function createIssuedOrder(body: IssuedOrderInput) {
  return prisma.$transaction(async (tx) => {
    const poNumber =
      body.po_number !== undefined &&
      body.po_number !== null &&
      body.po_number !== ""
        ? String(body.po_number)
        : (await generateIssuedOrderNumber(undefined, tx)).number;

    const order = await tx.issued_orders.create({
      data: {
        po_number: poNumber,
        customer_id: body.customer_id ? Number(body.customer_id) : null,
        status: (body.status
          ? String(body.status)
          : "draft") as $Enums.issued_orders_status,
        currency: body.currency ? String(body.currency) : "CZK",
        vat_rate: body.vat_rate != null ? Number(body.vat_rate) : 21.0,
        apply_vat: body.apply_vat !== false,
        exchange_rate:
          body.exchange_rate != null ? Number(body.exchange_rate) : 1.0,
        order_date: body.order_date
          ? new Date(String(body.order_date))
          : new Date(),
        delivery_date: body.delivery_date
          ? new Date(String(body.delivery_date))
          : null,
        language: body.language ? String(body.language) : "cs",
        delivery_terms: body.delivery_terms
          ? String(body.delivery_terms)
          : null,
        payment_terms: body.payment_terms ? String(body.payment_terms) : null,
        issued_by: body.issued_by ? String(body.issued_by) : null,
        notes: body.notes ? String(body.notes) : null,
        internal_notes: body.internal_notes
          ? String(body.internal_notes)
          : null,
      },
    });

    if (Array.isArray(body.items)) {
      await tx.issued_order_items.createMany({
        data: body.items.map((item, i) => ({
          issued_order_id: order.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,
          vat_rate: item.vat_rate ?? 21.0,
          position: item.position ?? i,
        })),
      });
    }

    return order;
  });
}

export async function updateIssuedOrder(id: number, body: IssuedOrderInput) {
  const existing = await prisma.issued_orders.findUnique({ where: { id } });
  if (!existing) return { error: "not_found" as const };

  const currentStatus = existing.status as string;

  if (body.status !== undefined && body.status !== currentStatus) {
    const newStatus = String(body.status);
    const allowed = VALID_TRANSITIONS[currentStatus] || [];
    if (!allowed.includes(newStatus)) {
      return { error: "invalid_transition" as const, currentStatus, newStatus };
    }
  }

  const editable = currentStatus === "draft" || currentStatus === "sent";
  const data: Record<string, unknown> = { modified_at: new Date() };

  if (editable) {
    const strFields = [
      "currency",
      "language",
      "delivery_terms",
      "payment_terms",
      "issued_by",
    ];
    for (const f of strFields) {
      if (body[f] !== undefined) data[f] = body[f] ? String(body[f]) : null;
    }
    if (body.customer_id !== undefined)
      data.customer_id = body.customer_id ? Number(body.customer_id) : null;
    if (body.vat_rate !== undefined) data.vat_rate = Number(body.vat_rate);
    if (body.apply_vat !== undefined)
      data.apply_vat =
        body.apply_vat === true ||
        body.apply_vat === 1 ||
        body.apply_vat === "1";
    if (body.exchange_rate !== undefined)
      data.exchange_rate =
        body.exchange_rate != null ? Number(body.exchange_rate) : null;
    if (body.order_date !== undefined)
      data.order_date = body.order_date
        ? new Date(String(body.order_date))
        : null;
    if (body.delivery_date !== undefined)
      data.delivery_date = body.delivery_date
        ? new Date(String(body.delivery_date))
        : null;
    if (body.notes !== undefined)
      data.notes = body.notes ? String(body.notes) : null;
    if (body.internal_notes !== undefined)
      data.internal_notes = body.internal_notes
        ? String(body.internal_notes)
        : null;
  }

  if (body.status !== undefined) data.status = String(body.status);

  await prisma.issued_orders.update({ where: { id }, data });

  if (editable && Array.isArray(body.items)) {
    await prisma.$transaction(async (tx) => {
      await tx.issued_order_items.deleteMany({
        where: { issued_order_id: id },
      });
      await tx.issued_order_items.createMany({
        data: body.items!.map((item, i) => ({
          issued_order_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,
          vat_rate: item.vat_rate ?? 21.0,
          position: item.position ?? i,
        })),
      });
    });
  }

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

export async function deleteIssuedOrder(id: number) {
  const existing = await prisma.issued_orders.findUnique({ where: { id } });
  if (!existing) return null;
  await prisma.issued_orders.delete({ where: { id } });
  // Reclaim the number against the year it was allocated in (the creation year),
  // not the current year — a cross-year delete must release the right sequence
  // or the number is never freed.
  const year = existing.created_at
    ? new Date(existing.created_at).getFullYear()
    : new Date().getFullYear();
  await releaseIssuedOrderNumber(year, existing.po_number ?? undefined);
  return existing;
}

export async function getNextIssuedOrderNumberPreview() {
  return previewIssuedOrderNumber();
}
