import * as z from "zod";
import type { StatusTone } from "@/components/app/status-badge";
import { countryCountSchema } from "@/lib/imports";

// Frontière réseau du module Intégrations (spec Imports §1.11, T8).
// Source de vérité des champs : `api/app/integrations/schemas.py`.
// Les dates sont typées `z.string()` (pas `z.iso.datetime()`) : le web les réaffiche,
// il ne calcule jamais dessus. Les objets ne sont pas `strict` : un champ ajouté côté api
// ne doit jamais casser l'écran.
//
// **Il n'y a volontairement aucun champ pour la clé.** Elle n'est pas « masquée à
// l'affichage » : l'api ne la rend pas, et ce schéma ne saurait pas où la mettre. Le seul
// écho est `credentials_hint` (`••••••cd12`).

/**
 * Fournisseurs BYOK — miroir de `PROVIDERS` (`api/app/integrations/models.py`).
 *
 * `signupUrl` et `tokenUrl` vivent **ici**, dans la définition du fournisseur, et pas dans le
 * JSX de l'écran : sans elles, un utilisateur qui n'a pas encore de clé n'a nulle part où
 * aller et la fonctionnalité paraît cassée. Les poser dans la définition fait que le prochain
 * fournisseur (DataForSEO, SE Ranking…) hérite du bloc sans qu'on y repense.
 */
export const PROVIDERS = [
  {
    value: "mangools",
    label: "Mangools — KWFinder",
    signupUrl: "https://mangools.com",
    tokenUrl: "https://mangools.com/api-token",
  },
] as const;

export type Provider = (typeof PROVIDERS)[number];

export function provider(name: string): Provider | null {
  return PROVIDERS.find((entry) => entry.value === name) ?? null;
}

export function providerLabel(name: string): string {
  return provider(name)?.label ?? name;
}

/** Bornes de saisie de la clé — miroir de `KEY_MIN` / `KEY_MAX`. */
export const KEY_MIN = 16;
export const KEY_MAX = 512;

export const INTEGRATION_STATUS_LABELS: Record<string, string> = {
  unverified: "Non vérifiée",
  connected: "Connectée",
  error: "En erreur",
};

/** L'état de la clé → le ton du badge partagé. Le rendu, lui, vit dans `StatusBadge`. */
export const INTEGRATION_STATUS_TONES: Record<string, StatusTone> = {
  unverified: "pending",
  connected: "success",
  error: "error",
};

// Pas de synchro récurrente (décision utilisateur 2026-09-02) : l'écran n'offre plus de
// cadence, donc plus de `SYNC_MODES`, plus de `syncModeSchema` et plus de `scheduleSchema`.
// La machinerie de planning reste côté api — voir `docs/pre-prod-checklist.md`.

// Format des dates de rapport. **Calculé côté serveur, jamais au rendu du
// composant** (même règle que `components/keywords/import-columns.tsx` et
// `components/sites/columns.tsx`) : `toLocaleString` dépend de la locale et du fuseau du
// runtime, donc le serveur et le navigateur produisent deux chaînes différentes et React
// signale un écart d'hydratation. Les Server Actions rendent donc le libellé déjà formaté,
// à côté de l'ISO — le composant n'a plus qu'à afficher une chaîne.
const momentFormat = new Intl.DateTimeFormat("fr-FR", {
  day: "2-digit",
  month: "2-digit",
  year: "numeric",
  hour: "2-digit",
  minute: "2-digit",
});

/** « 09/09/2026 14:30 ». Rend la valeur brute si elle n'est pas une date lisible. */
export function formatMoment(value: string): string {
  const parsed = new Date(value);
  return Number.isNaN(parsed.getTime()) ? value : momentFormat.format(parsed);
}

export const integrationSchema = z.object({
  id: z.uuid(),
  provider: z.string(),
  credentials_hint: z.string(),
  config: z.record(z.string(), z.unknown()),
  status: z.string(),
  status_error: z.string().nullable(),
  checked_at: z.string().nullable(),
  created_at: z.string(),
  updated_at: z.string(),
});

export const integrationListSchema = z.object({
  items: z.array(integrationSchema),
});

/** Le « dernier rapport » d'une cible — miroir d'`ImportSummary`. */
export const importSummarySchema = z.object({
  id: z.uuid(),
  status: z.string(),
  fetched_at: z.string(),
  created_at: z.string(),
  error: z.string().nullable(),
  report: z.record(z.string(), z.unknown()).nullable(),
});

/**
 * Le dernier import API d'une cible — miroir de `TargetImportOut`.
 *
 * Lu sur `GET /integrations/{id}/imports` et non sur `/schedules` : sans cadence, plus aucune
 * ligne `import_schedules` n'est créée, et une cible qui n'en a pas doit quand même montrer le
 * rapport de son dernier import manuel.
 */
export const targetImportSchema = z.object({
  site_target_id: z.uuid(),
  last_import: importSummarySchema,
});

export const targetImportListSchema = z.object({
  items: z.array(targetImportSchema),
});

/**
 * Le reste de quota du fournisseur — miroir de `QuotaOut`.
 *
 * `limit`/`remaining` peuvent être `null` : « le fournisseur n'a pas dit », que l'écran affiche
 * comme inconnu. Ni 0 ni le plafond ne sont des replis acceptables — l'un découragerait un
 * import que le fournisseur accepterait, l'autre en promettrait un qu'il refuserait.
 */
export const quotaSchema = z.object({
  provider: z.string(),
  resource: z.string(),
  limit: z.number().nullable(),
  remaining: z.number().nullable(),
  error: z.string().nullable(),
});

/**
 * Taille d'un lot d'enrichissement — miroir de `KEYWORD_IMPORTS_BATCH`
 * (`api/app/keywords/sources/mangools_api.py`).
 *
 * Elle vit ici parce que le récapitulatif du wizard doit annoncer un **nombre de requêtes**, et
 * que c'est la seule chose qui coûte : une unité de quota est consommée par requête, jamais par
 * mot-clé. Un lot de 200 coûte donc deux unités, pas deux cents.
 */
export const API_BATCH_SIZE = 100;

/** Le nombre de requêtes qu'un enrichissement de `count` mots-clés consommera. */
export function batchCount(count: number): number {
  return Math.max(1, Math.ceil(count / API_BATCH_SIZE));
}

/** Plafond de la liste d'enrichissement — miroir de `KEYWORDS_MAX`. */
export const KEYWORDS_MAX = 2000;

/** Longueur maximale d'un mot-clé — miroir de `KEYWORD_LENGTH_MAX`, elle-même celle de la base. */
export const KEYWORD_LENGTH_MAX = 255;

/**
 * Une saisie libre (un mot-clé par ligne) en liste propre : vides retirés, doublons écartés,
 * ordre de saisie conservé. Même nettoyage que `ImportTrigger._clean_keywords` côté api — fait
 * ici aussi pour que le récapitulatif compte ce qui partira vraiment, et non les lignes tapées.
 */
export function parseKeywordLines(text: string): string[] {
  const seen = new Set<string>();
  for (const line of text.split("\n")) {
    const term = line.trim();
    if (term !== "") seen.add(term);
  }
  return [...seen];
}

/**
 * Un import enfilé — miroir d'`ImportTriggerItem`.
 *
 * `list_id` / `list_name` ne sont renseignés qu'en mode liste : ce sont eux qui distinguent à
 * l'écran trois imports lancés d'un coup sur la même cible.
 */
export const importTriggerItemSchema = z.object({
  import_id: z.uuid(),
  job_id: z.uuid(),
  // Faux quand un import de la même période était déjà en vol : l'appel a rendu l'existant.
  created: z.boolean(),
  list_id: z.string().nullish(),
  list_name: z.string().nullish(),
});

/**
 * La réponse de `POST /integrations/{id}/import` — **une enveloppe, même pour un seul import**.
 *
 * Un déclenchement peut en enfiler plusieurs : « un import par (liste, cible) ». Garder des
 * champs plats aurait produit un contrat à deux lectures, et un écran qui n'en suit qu'un sur N
 * sans jamais le dire.
 */
export const importTriggerSchema = z.object({
  items: z.array(importTriggerItemSchema),
});

// --- listes déjà enregistrées dans le compte KWFinder -----------------------------------------
//
// **Ces deux endpoints ne consomment aucune unité de quota** (mesuré le 2026-09-02 : trois
// lectures de `/kwfinder/limits` encadrant un listing et un détail ont rendu vingt compteurs
// strictement identiques). C'est ce qui autorise le wizard à recenser les marchés d'une
// sélection avant même de choisir une cible.

/**
 * Plafond d'une sélection de listes — miroir de `LIST_IDS_MAX`.
 *
 * Il ne borne pas un coût mais une **attente** : le recensement ouvre une liste par requête, et
 * l'api les espace d'environ trois secondes pour respecter le débit du fournisseur.
 */
export const LIST_IDS_MAX = 10;

/**
 * Longueur maximale d'un identifiant de liste — miroir de `LIST_ID_LENGTH_MAX`.
 *
 * Il vit **ici**, avec les autres constantes miroir, et non dans le module d'actions : c'est ce
 * fichier qu'on relit pour vérifier qu'une borne n'a pas divergé de celle de l'api.
 */
export const LIST_ID_LENGTH_MAX = 64;

/** Une liste du compte — miroir de `KeywordListOut`. */
export const keywordListSchema = z.object({
  id: z.string(),
  name: z.string(),
  // Le compte du **fournisseur**, pas celui de ce qui sera importé : le cloisonnement par pays
  // en écarte une partie, et c'est le recensement puis le rapport qui le disent.
  keyword_count: z.number(),
  created_at: z.string().nullable(),
  updated_at: z.string().nullable(),
});

export const keywordListsSchema = z.object({
  items: z.array(keywordListSchema),
});

/** Le recensement d'une liste — miroir de `ListPreview`. */
export const listPreviewSchema = z.object({
  list_id: z.string(),
  name: z.string(),
  keywords_total: z.number(),
  countries: z.array(countryCountSchema).default([]),
});

export const listPreviewOutSchema = z.object({
  items: z.array(listPreviewSchema),
  countries: z.array(countryCountSchema).default([]),
});

export type Integration = z.infer<typeof integrationSchema>;
export type ImportSummary = z.infer<typeof importSummarySchema>;
export type ImportTriggerItem = z.infer<typeof importTriggerItemSchema>;
export type KeywordListRef = z.infer<typeof keywordListSchema>;
export type ListPreview = z.infer<typeof listPreviewSchema>;
export type Quota = z.infer<typeof quotaSchema>;
