/**
 * Petite base in-memory pour les mocks MSW.
 * Persiste dans sessionStorage pour survivre aux reload pendant le dev,
 * mais reste isolée par onglet (pas d'interférence entre devs).
 */
import type { Client, Invoice, Organization, Plan, User } from "@rubis/shared";
import { SEED_CLIENTS, SEED_INVOICES, SEED_PLANS } from "./seed";

const STORAGE_KEY = "rubis.mocks.db.v2";

type StoredUser = User & { passwordHash: string };

/** Forme enrichie des invoices stockée localement (avec dénormalisations
 *  pratiques pour les listes : clientName, planName, statusLabel). */
export type StoredInvoice = Invoice & {
  clientName: string;
  planName: string | null;
  statusLabel?: string;
};

/** Champs extraits par l'OCR (et leur version éditée par l'utilisateur). */
export type DraftFields = {
  /** Si l'OCR matche un client existant (case-insensitive sur le nom),
   *  on pré-remplit clientId pour que l'utilisateur n'ait pas à re-sélectionner.
   *  Sinon null = nouveau client à créer à la validation. */
  clientId: string | null;
  clientName: string;
  clientEmail: string | null;
  numero: string;
  amountTtcCents: number;
  issueDate: string;
  dueDate: string;
  planId: string | null;
};

/** Brouillon d'une facture en cours de review. */
export type StoredImportDraft = {
  id: string;
  filename: string;
  /** Champs initiaux extraits par "l'OCR" (notre mock). */
  extracted: DraftFields;
  /** Champs édités par l'utilisateur. Initialement = extracted. */
  edited: DraftFields;
  /** Confiance OCR par champ (0-1). Sert à signaler les champs douteux. */
  confidence: Partial<Record<keyof DraftFields, number>>;
  status: "pending" | "validated" | "skipped";
  /** Si validé, l'id de l'invoice créée. */
  invoiceId?: string;
};

export type StoredImportBatch = {
  id: string;
  organizationId: string;
  drafts: StoredImportDraft[];
  createdAt: string;
};

type Db = {
  users: StoredUser[];
  organizations: Organization[];
  clients: Client[];
  plans: Plan[];
  invoices: StoredInvoice[];
  importBatches: StoredImportBatch[];
};

const seedDb = (): Db => ({
  users: [
    {
      id: "usr_demo",
      email: "demo@rubis.fr",
      fullName: "Arthur Démo",
      organizationId: "org_demo",
      signature: "Cordialement,\nArthur — Rubis Démo",
      createdAt: new Date("2026-01-01").toISOString(),
      updatedAt: new Date().toISOString(),
      passwordHash: "demo1234",
    },
  ],
  organizations: [
    {
      id: "org_demo",
      name: "Rubis Démo",
      siret: null,
      monthlyVolumeBucket: "10-50",
      rubisCount: 124,
      createdAt: new Date("2026-01-01").toISOString(),
      updatedAt: new Date().toISOString(),
    },
  ],
  clients: SEED_CLIENTS,
  plans: SEED_PLANS,
  invoices: SEED_INVOICES,
  importBatches: [],
});

/**
 * Migration douce : si un champ top-level a été ajouté au schema entre
 * deux runs (ex. importBatches arrivé tardivement), on patche le snapshot
 * stocké avec le défaut du seed plutôt que de tout perdre. Évite les
 * `TypeError: Cannot read properties of undefined` pendant le dev.
 */
function migrate(stored: Partial<Db>): Db {
  const fresh = seedDb();
  // Les batches d'import seedés avant l'ajout de clientId sur DraftFields
  // sont incompatibles avec le combobox (no clientId). Plus simple de les
  // jeter à la migration : ils sont éphémères de toute façon (drafts
  // pending qu'on ne validera plus).
  const importBatches = (stored.importBatches ?? []).map((b) => ({
    ...b,
    drafts: b.drafts.map((d) => ({
      ...d,
      // Spread des champs stockés AVANT clientId, pour qu'on n'écrase pas
      // un clientId déjà présent dans un nouveau snapshot. La défaut `null`
      // ne s'applique que pour les vieux snapshots qui n'avaient pas le champ.
      extracted: { ...d.extracted, clientId: d.extracted.clientId ?? null },
      edited: { ...d.edited, clientId: d.edited.clientId ?? null },
    })),
  }));
  return {
    users: stored.users ?? fresh.users,
    organizations: stored.organizations ?? fresh.organizations,
    clients: stored.clients ?? fresh.clients,
    plans: stored.plans ?? fresh.plans,
    invoices: stored.invoices ?? fresh.invoices,
    importBatches,
  };
}

function load(): Db {
  if (typeof sessionStorage === "undefined") return seedDb();
  const raw = sessionStorage.getItem(STORAGE_KEY);
  if (!raw) {
    const fresh = seedDb();
    sessionStorage.setItem(STORAGE_KEY, JSON.stringify(fresh));
    return fresh;
  }
  try {
    const stored = JSON.parse(raw) as Partial<Db>;
    const migrated = migrate(stored);
    // Persiste la version migrée pour ne pas refaire le merge à chaque load.
    sessionStorage.setItem(STORAGE_KEY, JSON.stringify(migrated));
    return migrated;
  } catch {
    const fresh = seedDb();
    sessionStorage.setItem(STORAGE_KEY, JSON.stringify(fresh));
    return fresh;
  }
}

function save(db: Db): void {
  if (typeof sessionStorage !== "undefined") {
    sessionStorage.setItem(STORAGE_KEY, JSON.stringify(db));
  }
}

function stripHash(user: StoredUser): User {
  const { passwordHash: _ph, ...publicUser } = user;
  return publicUser;
}

export const mockDb = {
  // === Users ===
  findUserByEmail(email: string): StoredUser | undefined {
    return load().users.find((u) => u.email.toLowerCase() === email.toLowerCase());
  },
  findUserById(id: string): StoredUser | undefined {
    return load().users.find((u) => u.id === id);
  },
  createUser(input: { email: string; password: string; fullName: string }): User {
    const db = load();
    const now = new Date().toISOString();
    const orgId = `org_${crypto.randomUUID()}`;
    const userId = `usr_${crypto.randomUUID()}`;

    const org: Organization = {
      id: orgId,
      name: "",
      siret: null,
      monthlyVolumeBucket: null,
      rubisCount: 0,
      createdAt: now,
      updatedAt: now,
    };
    const user: StoredUser = {
      id: userId,
      email: input.email,
      fullName: input.fullName,
      organizationId: orgId,
      signature: null,
      createdAt: now,
      updatedAt: now,
      passwordHash: input.password,
    };
    db.users.push(user);
    db.organizations.push(org);
    save(db);
    return stripHash(user);
  },
  updateUser(
    id: string,
    patch: Partial<Pick<User, "fullName" | "email" | "signature">>,
  ): User | undefined {
    const db = load();
    const idx = db.users.findIndex((u) => u.id === id);
    if (idx === -1) return undefined;
    const existing = db.users[idx]!;
    const updated: StoredUser = { ...existing, ...patch, updatedAt: new Date().toISOString() };
    db.users[idx] = updated;
    save(db);
    return stripHash(updated);
  },

  // === Organizations ===
  findOrgById(id: string): Organization | undefined {
    return load().organizations.find((o) => o.id === id);
  },
  updateOrg(
    id: string,
    patch: Partial<Pick<Organization, "name" | "siret" | "monthlyVolumeBucket">>,
  ): Organization | undefined {
    const db = load();
    const idx = db.organizations.findIndex((o) => o.id === id);
    if (idx === -1) return undefined;
    const existing = db.organizations[idx]!;
    const updated: Organization = { ...existing, ...patch, updatedAt: new Date().toISOString() };
    db.organizations[idx] = updated;
    save(db);
    return updated;
  },

  // === Clients ===
  findClientById(orgId: string, id: string): Client | undefined {
    return load().clients.find((c) => c.organizationId === orgId && c.id === id);
  },
  listClientsForOrg(orgId: string): Client[] {
    return load().clients.filter((c) => c.organizationId === orgId);
  },
  createClient(
    orgId: string,
    input: Omit<Client, "id" | "organizationId" | "createdAt" | "updatedAt">,
  ): Client {
    const db = load();
    const now = new Date().toISOString();
    const client: Client = {
      ...input,
      id: `cli_${crypto.randomUUID()}`,
      organizationId: orgId,
      createdAt: now,
      updatedAt: now,
    };
    db.clients.push(client);
    save(db);
    return client;
  },
  updateClient(
    orgId: string,
    id: string,
    patch: Partial<Pick<Client, "name" | "email" | "phone" | "address" | "notes">>,
  ): Client | undefined {
    const db = load();
    const idx = db.clients.findIndex(
      (c) => c.organizationId === orgId && c.id === id,
    );
    if (idx === -1) return undefined;
    const existing = db.clients[idx]!;
    const updated: Client = {
      ...existing,
      ...patch,
      updatedAt: new Date().toISOString(),
    };
    db.clients[idx] = updated;
    save(db);
    return updated;
  },

  // === Plans ===
  findPlanById(orgId: string, id: string): Plan | undefined {
    return load().plans.find((p) => p.organizationId === orgId && p.id === id);
  },
  findPlanBySlug(orgId: string, slug: string): Plan | undefined {
    return load().plans.find(
      (p) => p.organizationId === orgId && p.slug === slug,
    );
  },
  listPlansForOrg(orgId: string): Plan[] {
    return load().plans.filter((p) => p.organizationId === orgId);
  },
  createPlan(
    orgId: string,
    input: Omit<Plan, "id" | "organizationId" | "createdAt" | "updatedAt">,
  ): Plan {
    const db = load();
    const now = new Date().toISOString();
    const plan: Plan = {
      ...input,
      id: `plan_${crypto.randomUUID()}`,
      organizationId: orgId,
      createdAt: now,
      updatedAt: now,
    };
    db.plans.push(plan);
    save(db);
    return plan;
  },
  updatePlan(
    orgId: string,
    id: string,
    patch: Partial<Pick<Plan, "name" | "description" | "steps">>,
  ): Plan | undefined {
    const db = load();
    const idx = db.plans.findIndex(
      (p) => p.organizationId === orgId && p.id === id,
    );
    if (idx === -1) return undefined;
    const existing = db.plans[idx]!;
    const updated: Plan = {
      ...existing,
      ...patch,
      updatedAt: new Date().toISOString(),
    };
    db.plans[idx] = updated;
    save(db);
    return updated;
  },

  // === Invoices ===
  listInvoicesForOrg(orgId: string): StoredInvoice[] {
    return load().invoices.filter((i) => i.organizationId === orgId);
  },
  createInvoice(orgId: string, input: Omit<StoredInvoice, "id" | "organizationId" | "createdAt" | "updatedAt">): StoredInvoice {
    const db = load();
    const now = new Date().toISOString();
    const invoice: StoredInvoice = {
      ...input,
      id: `inv_${crypto.randomUUID()}`,
      organizationId: orgId,
      createdAt: now,
      updatedAt: now,
    };
    db.invoices.push(invoice);
    save(db);
    return invoice;
  },
  findInvoiceById(orgId: string, id: string): StoredInvoice | undefined {
    return load().invoices.find((i) => i.organizationId === orgId && i.id === id);
  },
  updateInvoice(
    orgId: string,
    id: string,
    patch: Partial<StoredInvoice>,
  ): StoredInvoice | undefined {
    const db = load();
    const idx = db.invoices.findIndex(
      (i) => i.organizationId === orgId && i.id === id,
    );
    if (idx === -1) return undefined;
    const existing = db.invoices[idx]!;
    const updated: StoredInvoice = {
      ...existing,
      ...patch,
      updatedAt: new Date().toISOString(),
    };
    db.invoices[idx] = updated;
    save(db);
    return updated;
  },

  // === Import batches (OCR review flow) ===
  createImportBatch(
    orgId: string,
    drafts: Omit<StoredImportDraft, "id" | "status" | "edited">[],
  ): StoredImportBatch {
    const db = load();
    const batch: StoredImportBatch = {
      id: `batch_${crypto.randomUUID()}`,
      organizationId: orgId,
      createdAt: new Date().toISOString(),
      drafts: drafts.map((d) => ({
        ...d,
        id: `draft_${crypto.randomUUID()}`,
        edited: { ...d.extracted },
        status: "pending" as const,
      })),
    };
    db.importBatches.push(batch);
    save(db);
    return batch;
  },
  findImportBatch(orgId: string, id: string): StoredImportBatch | undefined {
    return load().importBatches.find(
      (b) => b.organizationId === orgId && b.id === id,
    );
  },
  updateImportDraft(
    orgId: string,
    batchId: string,
    draftId: string,
    patch: Partial<StoredImportDraft>,
  ): StoredImportDraft | undefined {
    const db = load();
    const batch = db.importBatches.find(
      (b) => b.organizationId === orgId && b.id === batchId,
    );
    if (!batch) return undefined;
    const idx = batch.drafts.findIndex((d) => d.id === draftId);
    if (idx === -1) return undefined;
    const updated = { ...batch.drafts[idx]!, ...patch };
    batch.drafts[idx] = updated;
    save(db);
    return updated;
  },
  deleteImportBatch(orgId: string, id: string): boolean {
    const db = load();
    const before = db.importBatches.length;
    db.importBatches = db.importBatches.filter(
      (b) => !(b.organizationId === orgId && b.id === id),
    );
    if (db.importBatches.length < before) {
      save(db);
      return true;
    }
    return false;
  },

  reset(): void {
    if (typeof sessionStorage !== "undefined") {
      sessionStorage.removeItem(STORAGE_KEY);
    }
  },
};
