import { http, HttpResponse } from "msw";
import { z } from "zod";
import type { Client } from "@rubis/shared";

import { mockDb, type StoredInvoice } from "../db";
import { userIdFromAuthHeader } from "./auth";

const apiBase = "*/api/v1";

function unauthenticated() {
  return HttpResponse.json(
    { errors: [{ code: "unauthenticated", message: "Non authentifié" }] },
    { status: 401 },
  );
}

function notFound() {
  return HttpResponse.json(
    { errors: [{ code: "not_found", message: "Client introuvable" }] },
    { status: 404 },
  );
}

function authedOrgId(authHeader: string | null): string | undefined {
  const userId = userIdFromAuthHeader(authHeader);
  if (!userId) return undefined;
  return mockDb.findUserById(userId)?.organizationId;
}

/**
 * Stats agrégées pour un client : compteurs + montants + dernière activité.
 * Calculées on-the-fly depuis les invoices — pas de cache, V1 c'est tenable.
 */
function computeStats(invoices: StoredInvoice[], now = new Date()) {
  const activeStatuses = new Set([
    "pending",
    "in_relance",
    "awaiting_user_confirmation",
  ]);

  let activeInvoiceCount = 0;
  let lateInvoiceCount = 0;
  let paidInvoiceCount = 0;
  let paidLifetimeCents = 0;
  let pendingLifetimeCents = 0;
  let lastActivityAt: string | null = null;

  for (const inv of invoices) {
    if (lastActivityAt === null || inv.updatedAt > lastActivityAt) {
      lastActivityAt = inv.updatedAt;
    }
    if (inv.status === "paid") {
      paidInvoiceCount += 1;
      paidLifetimeCents += inv.amountTtcCents;
    } else if (activeStatuses.has(inv.status)) {
      activeInvoiceCount += 1;
      pendingLifetimeCents += inv.amountTtcCents;
      if (new Date(inv.dueDate).getTime() < now.setHours(0, 0, 0, 0)) {
        lateInvoiceCount += 1;
      }
    }
  }
  return {
    invoiceCount: invoices.length,
    activeInvoiceCount,
    lateInvoiceCount,
    paidInvoiceCount,
    paidLifetimeCents,
    pendingLifetimeCents,
    lastActivityAt,
  };
}

const updateClientSchema = z.object({
  name: z.string().min(1).max(120).optional(),
  email: z.string().email("Email invalide").min(1).optional(),
  contactFirstName: z.string().min(1).max(80).nullable().optional(),
  contactLastName: z.string().min(1).max(80).nullable().optional(),
  phone: z.string().max(40).nullable().optional(),
  address: z.string().max(500).nullable().optional(),
  siret: z
    .string()
    .regex(/^\d{14}$/u, "Le SIRET doit contenir 14 chiffres")
    .nullable()
    .optional(),
  notes: z.string().max(2000).nullable().optional(),
});

const createClientSchema = z.object({
  name: z.string().min(2, "Au moins 2 caractères").max(120),
  // Email REQUIS : sans email, Rubis ne peut pas envoyer les relances
  // automatiques. C'est le pivot du produit, on n'accepte pas de fiche
  // client sans canal de communication actif.
  email: z.string().min(1, "Email requis").email("Format d'email invalide"),
  contactFirstName: z.string().min(1).max(80).nullable().optional().default(null),
  contactLastName: z.string().min(1).max(80).nullable().optional().default(null),
  phone: z.string().max(40).nullable().optional().default(null),
  address: z.string().max(500).nullable().optional().default(null),
  siret: z
    .string()
    .regex(/^\d{14}$/u, "Le SIRET doit contenir 14 chiffres")
    .nullable()
    .optional()
    .default(null),
  notes: z.string().max(2000).nullable().optional().default(null),
});

export const clientHandlers = [
  // GET /api/v1/clients?withStats=1&q=
  // Sans `withStats`, retour à plat (utilisé par le combobox).
  // Avec `withStats`, chaque client est enrichi des compteurs.
  http.get(`${apiBase}/clients`, ({ request }) => {
    const orgId = authedOrgId(request.headers.get("authorization"));
    if (!orgId) return unauthenticated();

    const url = new URL(request.url);
    const withStats = url.searchParams.get("withStats") === "1";
    const q = (url.searchParams.get("q") ?? "").toLowerCase().trim();

    const allInvoices = mockDb.listInvoicesForOrg(orgId);
    let clients: Client[] = mockDb.listClientsForOrg(orgId);

    if (q.length > 0) {
      clients = clients.filter(
        (c) =>
          c.name.toLowerCase().includes(q) ||
          c.email.toLowerCase().includes(q),
      );
    }

    if (!withStats) {
      return HttpResponse.json({ data: clients });
    }

    const enriched = clients.map((c) => {
      const invoices = allInvoices.filter((i) => i.clientId === c.id);
      return { ...c, ...computeStats(invoices) };
    });

    // Tri par défaut : ceux avec des retards d'abord (actionnable), puis
    // par activité récente.
    enriched.sort((a, b) => {
      if (a.lateInvoiceCount !== b.lateInvoiceCount) {
        return b.lateInvoiceCount - a.lateInvoiceCount;
      }
      const aLast = a.lastActivityAt ?? "";
      const bLast = b.lastActivityAt ?? "";
      return bLast.localeCompare(aLast);
    });

    return HttpResponse.json({ data: enriched });
  }),

  // POST /api/v1/clients — création manuelle (depuis /clients · "+ Nouveau client")
  http.post(`${apiBase}/clients`, async ({ request }) => {
    const orgId = authedOrgId(request.headers.get("authorization"));
    if (!orgId) return unauthenticated();

    const json = await request.json();
    const parsed = createClientSchema.safeParse(json);
    if (!parsed.success) {
      return HttpResponse.json(
        {
          errors: parsed.error.issues.map((i) => ({
            code: "validation_failed",
            message: i.message,
            field: i.path.join("."),
          })),
        },
        { status: 422 },
      );
    }

    // Détection de doublon par nom (case-insensitive). Pas de blocage dur :
    // on renvoie un 409 informatif que le SPA peut gérer pour proposer "voir
    // le client existant" plutôt que créer.
    const existing = mockDb
      .listClientsForOrg(orgId)
      .find((c) => c.name.toLowerCase() === parsed.data.name.toLowerCase());
    if (existing) {
      return HttpResponse.json(
        {
          errors: [
            {
              code: "duplicate_client",
              message: `Un client nommé « ${existing.name} » existe déjà.`,
              field: "name",
            },
          ],
          existing,
        },
        { status: 409 },
      );
    }

    const created = mockDb.createClient(orgId, {
      name: parsed.data.name,
      email: parsed.data.email,
      contactFirstName: parsed.data.contactFirstName,
      contactLastName: parsed.data.contactLastName,
      phone: parsed.data.phone,
      address: parsed.data.address,
      siret: parsed.data.siret,
      notes: parsed.data.notes,
    });
    return HttpResponse.json({ data: created }, { status: 201 });
  }),

  // GET /api/v1/clients/:id — détail enrichi avec invoices
  http.get(`${apiBase}/clients/:id`, ({ request, params }) => {
    const orgId = authedOrgId(request.headers.get("authorization"));
    if (!orgId) return unauthenticated();

    const id = params.id as string;
    const client = mockDb.findClientById(orgId, id);
    if (!client) return notFound();

    const invoices = mockDb
      .listInvoicesForOrg(orgId)
      .filter((i) => i.clientId === id)
      // Plus récent d'abord — l'utilisateur veut voir les factures fraîches
      .sort(
        (a, b) =>
          new Date(b.dueDate).getTime() - new Date(a.dueDate).getTime(),
      );

    return HttpResponse.json({
      data: {
        ...client,
        ...computeStats(invoices),
        invoices,
      },
    });
  }),

  // PATCH /api/v1/clients/:id — édition (nom, email, phone, address, notes)
  http.patch(`${apiBase}/clients/:id`, async ({ request, params }) => {
    const orgId = authedOrgId(request.headers.get("authorization"));
    if (!orgId) return unauthenticated();

    const id = params.id as string;
    const json = await request.json();
    const parsed = updateClientSchema.safeParse(json);
    if (!parsed.success) {
      return HttpResponse.json(
        {
          errors: parsed.error.issues.map((i) => ({
            code: "validation_failed",
            message: i.message,
            field: i.path.join("."),
          })),
        },
        { status: 422 },
      );
    }
    const updated = mockDb.updateClient(orgId, id, parsed.data);
    if (!updated) return notFound();
    return HttpResponse.json({ data: updated });
  }),
];
