import { lerConfigDoCurso, type ConfigDoCurso } from "./curso.ts";

/*
 * Regras do produto digital: o que vai para `products.digital_config` (PÚBLICO,
 * a loja manda para o navegador) e o que fica nos entregáveis (privado:
 * arquivo, link de acesso, instruções).
 *
 * Tudo o que entra pelas rotas passa por aqui: o banco guarda só o que foi
 * conferido e cortado no tamanho.
 */

export type TipoDigital = "arquivos" | "link" | "codigo" | "curso" | "live";
export const TIPOS_DIGITAIS: readonly TipoDigital[] = ["curso", "arquivos", "link", "codigo", "live"];

export const ROTULO_DO_TIPO: Record<TipoDigital, string> = {
  curso: "Conteúdo em vídeo",
  arquivos: "Arquivos para baixar",
  link: "Link de acesso",
  codigo: "Código ou licença",
  live: "Live",
};

export type TipoDeEntregavel = "arquivo" | "link" | "texto" | "codigos" | "curso" | "encomenda" | "live";

/* O que a página da loja mostra em "O que você recebe" (sem endereço nenhum). */
export type ItemRecebido = {
  titulo: string;
  detalhe: string | null;
  /* PDF, ZIP, MP4, LINK, TEXTO... para o selo colorido. */
  selo: string;
};

export type Pergunta = { pergunta: string; resposta: string };

/* Como o comprador recebe: na hora (sozinho) ou sob encomenda (a loja produz
   depois da compra e anexa o arquivo final no pedido). */
export type ComoEntrega = { modo: "na_hora" | "encomenda"; prazoDias: number };

export type ConfigDigital = {
  tipo: TipoDigital;
  /* Uma frase embaixo do nome, na loja. */
  resumo: string | null;
  recebe: ItemRecebido[];
  perguntas: Pergunta[];
  funcionaCom: string[];
  licenca: { tipo: "pessoal" | "comercial" | "livre" | "texto"; texto: string | null } | null;
  acesso: { prazoDias: number | null } | null;
  amostras: string[];
  /* Arquivos e link: na hora ou sob encomenda (prazo em dias úteis). */
  entrega: ComoEntrega;
  /* Pré-venda: vende hoje e libera sozinho nesta data (ISO). */
  preVenda: { em: string } | null;
  /* Quantas vezes cada arquivo pode ser baixado (null: sem limite, com o freio). */
  limiteDeDownloads: number | null;
  /* Marcar o PDF com nome, e-mail e número do pedido. null: segue as Configurações. */
  marcaNoPdf: boolean | null;
  /* Código ou licença: avisar quando restarem estes códigos livres. */
  avisarCodigosCom: number;
  /* Só nos cursos: a página de venda (o que vai aprender, quem ensina,
     apresentação, garantia) e o resumo público do conteúdo. */
  curso?: ConfigDoCurso;
};

export const ENTREGA_PADRAO: ComoEntrega = { modo: "na_hora", prazoDias: 3 };

function inteiroNaFaixa(valor: unknown, padrao: number, minimo: number, maximo: number): number {
  const numero = typeof valor === "number" ? valor : typeof valor === "string" && valor.trim() ? Number(valor) : NaN;
  if (!Number.isFinite(numero)) return padrao;
  return Math.min(maximo, Math.max(minimo, Math.round(numero)));
}

/* A data da pré-venda guardada (ISO), ou null quando não vale. */
export function lerPreVenda(valor: unknown): { em: string } | null {
  const bruto = valor && typeof valor === "object" && !Array.isArray(valor) ? (valor as Record<string, unknown>).em : null;
  if (typeof bruto !== "string" || !/^\d{4}-\d{2}-\d{2}/.test(bruto)) return null;
  const t = new Date(bruto).getTime();
  return Number.isFinite(t) ? { em: new Date(t).toISOString() } : null;
}

function objeto(valor: unknown): Record<string, unknown> {
  return valor && typeof valor === "object" && !Array.isArray(valor) ? (valor as Record<string, unknown>) : {};
}

function textoLimpo(valor: unknown, teto: number): string | null {
  if (typeof valor !== "string") return null;
  const limpo = valor.replace(/\u0000/g, "").trim();
  return limpo ? limpo.slice(0, teto) : null;
}

function enderecoHttps(valor: unknown): string | null {
  const bruto = textoLimpo(valor, 1000);
  if (!bruto) return null;
  try {
    const url = new URL(bruto);
    return url.protocol === "https:" ? url.toString() : null;
  } catch {
    return null;
  }
}

export function tipoDigitalValido(valor: unknown): TipoDigital | null {
  return typeof valor === "string" && (TIPOS_DIGITAIS as readonly string[]).includes(valor) ? (valor as TipoDigital) : null;
}

export function lerConfigDigital(bruto: unknown, tipoPadrao: TipoDigital = "arquivos"): ConfigDigital {
  const raiz = objeto(bruto);
  const recebe = Array.isArray(raiz.recebe) ? raiz.recebe : [];
  const perguntas = Array.isArray(raiz.perguntas) ? raiz.perguntas : [];
  const funcionaCom = Array.isArray(raiz.funcionaCom) ? raiz.funcionaCom : [];
  const amostras = Array.isArray(raiz.amostras) ? raiz.amostras : [];
  const licenca = objeto(raiz.licenca);
  const acesso = objeto(raiz.acesso);
  const tipoDaLicenca = ["pessoal", "comercial", "livre", "texto"].includes(String(licenca.tipo)) ? (licenca.tipo as "pessoal" | "comercial" | "livre" | "texto") : null;
  const prazo = acesso.prazoDias;

  return {
    tipo: tipoDigitalValido(raiz.tipo) ?? tipoPadrao,
    resumo: textoLimpo(raiz.resumo, 160),
    recebe: recebe
      .slice(0, 30)
      .map((item) => {
        const o = objeto(item);
        const titulo = textoLimpo(o.titulo, 160);
        if (!titulo) return null;
        return { titulo, detalhe: textoLimpo(o.detalhe, 200), selo: (textoLimpo(o.selo, 8) ?? "ITEM").toUpperCase() };
      })
      .filter((item): item is ItemRecebido => item !== null),
    perguntas: perguntas
      .slice(0, 12)
      .map((item) => {
        const o = objeto(item);
        const pergunta = textoLimpo(o.pergunta, 200);
        const resposta = textoLimpo(o.resposta, 1200);
        return pergunta && resposta ? { pergunta, resposta } : null;
      })
      .filter((item): item is Pergunta => item !== null),
    funcionaCom: funcionaCom
      .map((item) => textoLimpo(item, 40))
      .filter((item): item is string => Boolean(item))
      .slice(0, 12),
    licenca: tipoDaLicenca ? { tipo: tipoDaLicenca, texto: textoLimpo(licenca.texto, 2000) } : null,
    acesso:
      prazo === undefined
        ? null
        : { prazoDias: typeof prazo === "number" && Number.isFinite(prazo) && prazo > 0 ? Math.min(3650, Math.round(prazo)) : null },
    amostras: amostras
      .map(enderecoHttps)
      .filter((item): item is string => Boolean(item))
      .slice(0, 8),
    entrega: (() => {
      const entrega = objeto(raiz.entrega);
      return {
        modo: entrega.modo === "encomenda" ? "encomenda" : "na_hora",
        prazoDias: inteiroNaFaixa(entrega.prazoDias, ENTREGA_PADRAO.prazoDias, 0, 90),
      } satisfies ComoEntrega;
    })(),
    preVenda: lerPreVenda(raiz.preVenda),
    limiteDeDownloads:
      raiz.limiteDeDownloads === null || raiz.limiteDeDownloads === undefined
        ? null
        : inteiroNaFaixa(raiz.limiteDeDownloads, 0, 0, 1000) || null,
    marcaNoPdf: typeof raiz.marcaNoPdf === "boolean" ? raiz.marcaNoPdf : null,
    avisarCodigosCom: inteiroNaFaixa(raiz.avisarCodigosCom, 5, 0, 1000),
    ...((tipoDigitalValido(raiz.tipo) ?? tipoPadrao) === "curso" ? { curso: lerConfigDoCurso(raiz.curso) } : {}),
  };
}

/* O selo de um entregável na loja. */
export function seloDoEntregavel(tipo: TipoDeEntregavel, extensaoDoArquivo?: string | null): string {
  if (tipo === "arquivo") return (extensaoDoArquivo || "arq").slice(0, 4).toUpperCase();
  if (tipo === "link") return "LINK";
  if (tipo === "texto") return "TEXTO";
  if (tipo === "codigos") return "CÓD";
  if (tipo === "encomenda") return "SOB";
  return "VÍDEO";
}

/* "O que você recebe" montado a partir dos entregáveis (sem segredo nenhum). */
export function resumoDoQueRecebe(
  entregaveis: Array<{ tipo: TipoDeEntregavel; titulo: string; descricao: string | null; ativo: boolean; arquivo?: { extensao: string; tamanhoLegivel: string } | null }>
): ItemRecebido[] {
  return entregaveis
    .filter((e) => e.ativo)
    .slice(0, 30)
    .map((e) => {
      const partes: string[] = [];
      if (e.descricao) partes.push(e.descricao);
      if (e.tipo === "arquivo" && e.arquivo) partes.push(`${e.arquivo.extensao.toUpperCase()} · ${e.arquivo.tamanhoLegivel}`);
      if (e.tipo === "link") partes.push("Acesso liberado depois do pagamento");
      if (e.tipo === "codigos") partes.push("Um código para cada unidade comprada");
      if (e.tipo === "encomenda") partes.push("Feito para você depois da compra");
      return {
        titulo: e.titulo.slice(0, 160),
        detalhe: partes.length ? partes.join(" · ").slice(0, 200) : null,
        selo: seloDoEntregavel(e.tipo, e.arquivo?.extensao ?? null),
      };
    });
}

/* Preço em reais com duas casas; nunca negativo nem absurdo. */
export function precoValido(valor: unknown): number | null {
  const numero = typeof valor === "number" ? valor : typeof valor === "string" ? Number(valor.replace(",", ".")) : NaN;
  if (!Number.isFinite(numero) || numero < 0 || numero > 1_000_000) return null;
  return Math.round(numero * 100) / 100;
}

export type ConferenciaDoLink = { ok: true; url: string } | { ok: false; motivo: string };

/* Link de acesso: http(s), sem espaço, sem javascript:. */
export function conferirLinkDeAcesso(valor: unknown): ConferenciaDoLink {
  const bruto = textoLimpo(valor, 2000);
  if (!bruto) return { ok: false, motivo: "Cole o link que o comprador vai receber." };
  let url: URL;
  try {
    url = new URL(bruto);
  } catch {
    return { ok: false, motivo: "Esse link não parece um endereço. Ele precisa começar com https://" };
  }
  if (url.protocol !== "https:" && url.protocol !== "http:") return { ok: false, motivo: "O link precisa começar com https://" };
  if (!url.hostname.includes(".")) return { ok: false, motivo: "Confira o endereço do link." };
  return { ok: true, url: url.toString() };
}

/* O produto pode ir para a loja? (o que falta, em português). */
export function pendenciasParaPublicar(produto: {
  nome: string;
  preco: number;
  imagem: string | null;
  entregaveisAtivos: number;
  arquivosNaoProntos: number;
  /* Código ou licença: quantos códigos livres há (null: não é desse tipo). */
  codigosLivres?: number | null;
  /* Produto grátis numa loja que ainda não assina (no período de teste). */
  gratisSemAssinatura?: boolean;
}): string[] {
  const faltas: string[] = [];
  if (!produto.nome.trim()) faltas.push("Dê um nome ao produto.");
  if (!produto.imagem) faltas.push("Coloque uma capa: é ela que aparece na vitrine.");
  if (produto.entregaveisAtivos === 0) faltas.push("Adicione o que o comprador recebe.");
  if (produto.arquivosNaoProntos > 0) faltas.push("Espere os arquivos terminarem de enviar.");
  if (produto.codigosLivres === 0) faltas.push("Adicione os códigos: cada compra leva um.");
  if (produto.gratisSemAssinatura) faltas.push("Produto grátis é para quem assina o Ktalogu. No período de teste, coloque um preço.");
  return faltas;
}

/* Quantos dias úteis depois de hoje (sábado e domingo não contam). */
export function diaUtilDepois(inicio: Date, dias: number): Date {
  const dia = new Date(Date.UTC(inicio.getUTCFullYear(), inicio.getUTCMonth(), inicio.getUTCDate()));
  let faltam = Math.max(0, Math.min(365, Math.round(dias)));
  while (faltam > 0) {
    dia.setUTCDate(dia.getUTCDate() + 1);
    const semana = dia.getUTCDay();
    if (semana !== 0 && semana !== 6) faltam -= 1;
  }
  return dia;
}

/* O que o comprador precisa informar no sob encomenda (nome no convite, data
   da festa...). Mesmo formato dos campos personalizados dos produtos físicos
   (`products.custom_fields_config`): a loja e o pedido já sabem ler. */
export type CampoDoComprador = {
  id: string;
  title: string;
  field_type: "text" | "textarea" | "number";
  placeholder: string;
  required: boolean;
  active: boolean;
  position: number;
};

export function lerCamposDoComprador(valor: unknown): CampoDoComprador[] {
  if (!Array.isArray(valor)) return [];
  const vistos = new Set<string>();
  return valor
    .slice(0, 12)
    .map((item, indice): CampoDoComprador | null => {
      const o = objeto(item);
      const title = textoLimpo(o.title, 80);
      if (!title) return null;
      let id = textoLimpo(o.id, 40)?.replace(/[^A-Za-z0-9_-]/g, "") || `cf_${indice + 1}`;
      if (vistos.has(id)) id = `${id}_${indice + 1}`;
      vistos.add(id);
      const tipo = o.field_type === "textarea" || o.field_type === "number" ? o.field_type : "text";
      return {
        id,
        title,
        field_type: tipo,
        placeholder: textoLimpo(o.placeholder, 120) ?? "",
        required: o.required === true,
        active: o.active !== false,
        position: indice,
      };
    })
    .filter((campo): campo is CampoDoComprador => campo !== null);
}
