import { createServerFn } from "@tanstack/react-start";
import { z } from "zod";
import { requireSupabaseAuth } from "@/integrations/supabase/auth-middleware";

const STATUSES = ["draft", "sent", "viewed", "accepted", "rejected", "expired"] as const;

const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;

const CreateInput = z.object({
  companyId: z.string().uuid().nullish(),
  /** Customer account ID (UUID) or the email address they signed up with. */
  userId: z.string().min(1, "Enter the customer's email address or account ID."),
  scanId: z.string().uuid().nullish(),
  bookingId: z.string().uuid().nullish(),
  country: z.enum(["NG", "US", "GB"]).default("NG"),
  currency: z.string().default("NGN"),
  customerName: z.string().max(160).nullish(),
  customerEmail: z.string().max(160).nullish(),
  customerPhone: z.string().max(60).nullish(),
  propertyAddress: z.string().max(400).nullish(),
  notes: z.string().max(2000).nullish(),
  discount: z.number().min(0).default(0),
  vatRate: z.number().min(0).max(100).default(7.5),
  validUntil: z.string().nullish(),
  items: z
    .array(
      z.object({
        description: z.string().max(400).default(""),
        quantity: z.number().min(0).default(1),
        unit_price: z.number().min(0).default(0),
      }),
    )
    .min(1),
});

/** Accepts a UUID or an email address and returns the customer's account ID. */
async function resolveCustomerId(value: string) {
  const input = value.trim();
  if (UUID.test(input)) return input;
  if (!input.includes("@")) throw new Error("Enter a valid customer email address or account ID.");
  const { supabaseAdmin } = await import("@/integrations/supabase/client.server");
  const { data, error } = await supabaseAdmin.auth.admin.listUsers({ page: 1, perPage: 1000 });
  if (error) throw new Error(error.message);
  const match = data.users.find((u) => u.email?.toLowerCase() === input.toLowerCase());
  if (!match) throw new Error(`No customer account found for ${input}. Ask them to register first.`);
  return match.id;
}

const StatusInput = z.object({
  quotationId: z.string().uuid(),
  status: z.enum(STATUSES),
  note: z.string().max(500).nullish(),
});

const CUSTOMER_MESSAGE: Record<string, { title: string; body: string }> = {
  sent: { title: "New quotation received", body: "A service provider sent you a quotation. Review and accept it in your portal." },
  accepted: { title: "Quotation accepted", body: "Your quotation was accepted and an invoice has been created." },
  rejected: { title: "Quotation rejected", body: "The quotation was marked as rejected." },
  expired: { title: "Quotation expired", body: "A quotation passed its validity date and has expired." },
};

function totals(items: { quantity: number; unit_price: number }[], discount: number, vatRate: number) {
  const subtotal = items.reduce((sum, i) => sum + Number(i.quantity || 0) * Number(i.unit_price || 0), 0);
  const net = Math.max(0, subtotal - discount);
  const vatAmount = Math.round(net * (vatRate / 100) * 100) / 100;
  return {
    subtotal: Math.round(subtotal * 100) / 100,
    vatAmount,
    total: Math.round((net + vatAmount) * 100) / 100,
  };
}

async function canManage(userId: string, companyId?: string | null) {
  const { supabaseAdmin } = await import("@/integrations/supabase/client.server");
  const { data: isAdmin } = await supabaseAdmin.rpc("has_role", { _user_id: userId, _role: "admin" });
  if (isAdmin) return true;
  if (!companyId) return false;
  const { data } = await supabaseAdmin
    .from("company_members")
    .select("id")
    .eq("company_id", companyId)
    .eq("user_id", userId)
    .eq("active", true)
    .maybeSingle();
  return Boolean(data);
}

/** Providers and Emma admins raise an itemised quotation from a scan, booking or report. */
export const createQuotation = createServerFn({ method: "POST" })
  .middleware([requireSupabaseAuth])
  .inputValidator((input: unknown) => CreateInput.parse(input))
  .handler(async ({ data, context }) => {
    if (!(await canManage(context.userId, data.companyId)))
      throw new Error("Only the service provider's team or Emma Exclusive Teams can raise a quotation.");

    const items = data.items
      .map((i) => ({ ...i, description: i.description.trim() }))
      .filter((i) => i.description.length > 0);
    if (!items.length) throw new Error("Add at least one line item with a description.");
    const customerId = await resolveCustomerId(data.userId);

    const { supabaseAdmin } = await import("@/integrations/supabase/client.server");
    const sums = totals(items, data.discount, data.vatRate);
    const reference = `QTE-${new Date().toISOString().slice(0, 10).replace(/-/g, "")}-${Math.random().toString(36).slice(2, 6).toUpperCase()}`;

    const { data: quote, error } = await supabaseAdmin
      .from("quotations")
      .insert({
        reference,
        user_id: customerId,
        company_id: data.companyId ?? null,
        scan_id: data.scanId ?? null,
        booking_id: data.bookingId ?? null,
        status: "draft",
        country: data.country,
        currency: data.currency,
        customer_name: data.customerName ?? null,
        customer_email: data.customerEmail ?? null,
        customer_phone: data.customerPhone ?? null,
        property_address: data.propertyAddress ?? null,
        notes: data.notes ?? null,
        discount: data.discount,
        vat_rate: data.vatRate,
        vat_amount: sums.vatAmount,
        subtotal: sums.subtotal,
        total: sums.total,
        valid_until: data.validUntil ?? null,
      })
      .select("id,reference")
      .single();
    if (error) throw new Error(error.message);

    const { error: itemError } = await supabaseAdmin
      .from("quotation_items")
      .insert(items.map((i) => ({ ...i, quotation_id: quote.id })));
    if (itemError) throw new Error(itemError.message);

    await supabaseAdmin.from("quotation_events").insert({
      quotation_id: quote.id,
      to_status: "draft",
      actor_id: context.userId,
      note: "Quotation created",
    });

    return { id: quote.id, reference: quote.reference };
  });

/** Move a quotation through Draft → Sent → Viewed → Accepted/Rejected/Expired, invoicing on acceptance. */
export const setQuotationStatus = createServerFn({ method: "POST" })
  .middleware([requireSupabaseAuth])
  .inputValidator((input: unknown) => StatusInput.parse(input))
  .handler(async ({ data, context }) => {
    const { supabaseAdmin } = await import("@/integrations/supabase/client.server");
    const { data: quote, error } = await supabaseAdmin
      .from("quotations")
      .select("*")
      .eq("id", data.quotationId)
      .maybeSingle();
    if (error) throw new Error(error.message);
    if (!quote) throw new Error("That quotation no longer exists.");

    const isCustomer = quote.user_id === context.userId;
    const manages = await canManage(context.userId, quote.company_id);
    // Customers may only view, accept or reject their own quotation.
    const customerAllowed = ["viewed", "accepted", "rejected"].includes(data.status);
    if (!manages && !(isCustomer && customerAllowed)) throw new Error("You cannot change this quotation.");

    const now = new Date().toISOString();
    const stamps: Record<string, string> = {
      sent: "sent_at",
      viewed: "viewed_at",
      accepted: "accepted_at",
      rejected: "rejected_at",
      expired: "expired_at",
    };
    const patch: Record<string, string> = { status: data.status, updated_at: now };
    if (stamps[data.status]) patch[stamps[data.status]] = now;

    const { error: updateError } = await supabaseAdmin.from("quotations").update(patch as Record<string, never>).eq("id", quote.id);
    if (updateError) throw new Error(updateError.message);

    await supabaseAdmin.from("quotation_events").insert({
      quotation_id: quote.id,
      from_status: quote.status,
      to_status: data.status,
      actor_id: context.userId,
      note: data.note ?? null,
    });
    await supabaseAdmin.from("audit_logs").insert({
      actor_id: context.userId,
      action: `Quotation ${data.status}`,
      entity: quote.reference,
      entity_id: quote.id,
    });

    const message = CUSTOMER_MESSAGE[data.status];
    if (message) {
      await supabaseAdmin.from("notifications").insert({
        user_id: quote.user_id,
        title: `${message.title} — ${quote.reference}`,
        body: message.body,
      });
    }

    let invoice: { id: string; invoice_no: string } | null = null;
    if (data.status === "accepted") {
      const { data: existing } = await supabaseAdmin
        .from("invoices")
        .select("id,invoice_no")
        .eq("quotation_id", quote.id)
        .maybeSingle();
      if (existing) {
        invoice = existing;
      } else {
        const { data: items } = await supabaseAdmin
          .from("quotation_items")
          .select("description,quantity,unit_price")
          .eq("quotation_id", quote.id);
        const company = quote.company_id
          ? (
              await supabaseAdmin
                .from("companies")
                .select("bank_name,bank_account_name,bank_account_number")
                .eq("id", quote.company_id)
                .maybeSingle()
            ).data
          : null;
        const invoiceNo = `INV-${new Date().toISOString().slice(0, 10).replace(/-/g, "")}-${Math.random().toString(36).slice(2, 6).toUpperCase()}`;
        const { data: created, error: invoiceError } = await supabaseAdmin
          .from("invoices")
          .insert({
            invoice_no: invoiceNo,
            quotation_id: quote.id,
            company_id: quote.company_id,
            user_id: quote.user_id,
            country: quote.country,
            currency: quote.currency,
            customer_name: quote.customer_name,
            customer_email: quote.customer_email,
            customer_phone: quote.customer_phone,
            property_address: quote.property_address,
            subtotal: quote.subtotal,
            discount: quote.discount,
            vat_rate: quote.vat_rate,
            vat_amount: quote.vat_amount,
            total: quote.total,
            status: "unpaid",
            bank_name: company?.bank_name ?? "Stanbic IBTC Bank",
            bank_account_name: company?.bank_account_name ?? "Idowu Segun",
            bank_account_number: company?.bank_account_number ?? "0036325455",
            notes: quote.notes,
          })
          .select("id,invoice_no")
          .single();
        if (invoiceError) throw new Error(invoiceError.message);
        invoice = created;
        if (items?.length) {
          await supabaseAdmin.from("invoice_items").insert(items.map((i) => ({ ...i, invoice_id: created.id })));
        }
        await supabaseAdmin.from("notifications").insert({
          user_id: quote.user_id,
          title: `Invoice ${created.invoice_no} issued`,
          body: "Your accepted quotation has been converted into an invoice. Download it from your portal.",
        });
      }
    }

    return { id: quote.id, status: data.status, invoice };
  });

/** Mark an invoice as paid / partially paid / void. */
export const setInvoiceStatus = createServerFn({ method: "POST" })
  .middleware([requireSupabaseAuth])
  .inputValidator((input: unknown) =>
    z
      .object({
        invoiceId: z.string().uuid(),
        status: z.enum(["unpaid", "partially_paid", "paid", "overdue", "void"]),
        amountPaid: z.number().min(0).optional(),
      })
      .parse(input),
  )
  .handler(async ({ data, context }) => {
    const { supabaseAdmin } = await import("@/integrations/supabase/client.server");
    const { data: invoice } = await supabaseAdmin
      .from("invoices")
      .select("id,invoice_no,company_id,user_id,total")
      .eq("id", data.invoiceId)
      .maybeSingle();
    if (!invoice) throw new Error("That invoice no longer exists.");
    if (!(await canManage(context.userId, invoice.company_id)))
      throw new Error("Only the issuing provider or Emma Exclusive Teams can update an invoice.");

    const patch: Record<string, string | number> = { status: data.status, updated_at: new Date().toISOString() };
    if (data.amountPaid != null) patch.amount_paid = data.amountPaid;
    if (data.status === "paid") patch.amount_paid = invoice.total;
    const { error } = await supabaseAdmin.from("invoices").update(patch as Record<string, never>).eq("id", invoice.id);
    if (error) throw new Error(error.message);

    await supabaseAdmin.from("notifications").insert({
      user_id: invoice.user_id,
      title: `Invoice ${invoice.invoice_no} — ${data.status.replace("_", " ")}`,
      body: "Your invoice status was updated.",
    });
    await supabaseAdmin.from("audit_logs").insert({
      actor_id: context.userId,
      action: `Invoice ${data.status}`,
      entity: invoice.invoice_no,
      entity_id: invoice.id,
    });
    return { id: invoice.id, status: data.status };
  });