import type { SupabaseClient } from "@supabase/supabase-js";

import { ErroDigital } from "./contexto";

/*
 * "Alunos e acessos": quem tem acesso a cada produto digital, até quando, e
 * as mãos da loja sobre isso (bloquear, reativar, mudar o prazo, dar acesso
 * de cortesia).
 *
 * Um "aluno" na lista é uma pessoa num produto: os acessos aos itens do mesmo
 * produto andam juntos.
 */

export type SituacaoDoAluno = "ativo" | "preparando" | "bloqueado" | "suspenso" | "vencido" | "cancelado";

export type AlunoNaLista = {
  chave: string;
  clienteId: string;
  nome: string;
  email: string | null;
  produtoId: string;
  produto: string;
  imagem: string | null;
  tipo: string;
  origem: "pedido" | "cortesia";
  situacao: SituacaoDoAluno;
  liberadoEm: string | null;
  expiraEm: string | null;
  ultimoAcessoEm: string | null;
  downloads: number;
  itens: number;
  pedido: { id: string; numero: number | null } | null;
  /* Só nos cursos: aulas concluídas sobre as aulas do curso, em %. */
  progresso: number | null;
  /* Só nos cursos: o certificado emitido e o último "continue de onde parou". */
  certificado: { codigo: string; emitidoEm: string } | null;
  lembreteEm: string | null;
};

type Linha = {
  id: string;
  client_id: string;
  product_id: string;
  order_id: string | null;
  origem: "pedido" | "cortesia";
  status: string;
  liberado_em: string | null;
  expira_em: string | null;
  ultimo_acesso_em: string | null;
  downloads: number;
  motivo: string | null;
  lembrete_em: string | null;
  clients: { name: string | null; email: string | null } | null;
  products: { name: string | null; image_url: string | null; digital_config: { tipo?: string; curso?: { conteudo?: { aulas?: number } } } | null } | null;
  orders: { number: number | null } | null;
};

const ORDEM: SituacaoDoAluno[] = ["ativo", "preparando", "suspenso", "bloqueado", "vencido", "cancelado"];

export function situacaoDoGrupo(linhas: Array<{ status: string; expira_em: string | null }>, agora = Date.now()): SituacaoDoAluno {
  const situacoes = linhas.map((l): SituacaoDoAluno => {
    if (l.status === "ativo") return l.expira_em && new Date(l.expira_em).getTime() < agora ? "vencido" : "ativo";
    if (l.status === "aguardando_arquivo" || l.status === "aguardando_codigo" || l.status === "agendado") return "preparando";
    if (l.status === "pausado") return "bloqueado";
    if (l.status === "suspenso") return "suspenso";
    if (l.status === "expirado") return "vencido";
    return "cancelado";
  });
  for (const situacao of ORDEM) if (situacoes.includes(situacao)) return situacao;
  return "cancelado";
}

export async function listarAlunos(admin: SupabaseClient, lojaId: string): Promise<AlunoNaLista[]> {
  const { data, error } = await admin
    .from("digital_entitlements")
    .select(
      "id,client_id,product_id,order_id,origem,status,liberado_em,expira_em,ultimo_acesso_em,downloads,motivo,lembrete_em,clients(name,email),products(name,image_url,digital_config),orders(number)"
    )
    .eq("user_id", lojaId)
    .order("criado_em", { ascending: false })
    .limit(2000);
  if (error) throw new ErroDigital("Não foi possível carregar os clientes.", 500);

  const grupos = new Map<string, Linha[]>();
  for (const linha of (data ?? []) as unknown as Linha[]) {
    const chave = `${linha.client_id}:${linha.product_id}`;
    const lista = grupos.get(chave) ?? [];
    lista.push(linha);
    grupos.set(chave, lista);
  }

  /* O progresso de quem faz curso: aulas concluídas por pessoa e curso. */
  const cursos = new Set(
    Array.from(grupos.values())
      .map((linhas) => linhas[0])
      .filter((l) => l.products?.digital_config?.tipo === "curso")
      .map((l) => l.product_id)
  );
  const concluidas = new Map<string, number>();
  const certificados = new Map<string, { codigo: string; emitidoEm: string }>();
  if (cursos.size) {
    const [{ data: progresso }, { data: emitidos }] = await Promise.all([
      admin
        .from("digital_course_progress")
        .select("client_id,product_id")
        .eq("user_id", lojaId)
        .in("product_id", Array.from(cursos))
        .not("concluida_em", "is", null)
        .limit(20000),
      admin
        .from("digital_certificates")
        .select("client_id,product_id,codigo,emitido_em")
        .eq("user_id", lojaId)
        .in("product_id", Array.from(cursos))
        .is("revogado_em", null)
        .limit(20000),
    ]);
    for (const linha of (progresso ?? []) as Array<{ client_id: string; product_id: string }>) {
      const chave = `${linha.client_id}:${linha.product_id}`;
      concluidas.set(chave, (concluidas.get(chave) ?? 0) + 1);
    }
    for (const linha of (emitidos ?? []) as Array<{ client_id: string; product_id: string; codigo: string; emitido_em: string }>) {
      certificados.set(`${linha.client_id}:${linha.product_id}`, { codigo: linha.codigo, emitidoEm: linha.emitido_em });
    }
  }

  return Array.from(grupos.entries()).map(([chave, linhas]) => {
    const primeira = linhas[0];
    const comPedido = linhas.find((l) => l.order_id);
    const prazos = linhas.map((l) => l.expira_em).filter((d): d is string => Boolean(d)).sort();
    const acessos = linhas.map((l) => l.ultimo_acesso_em).filter((d): d is string => Boolean(d)).sort();
    const liberacoes = linhas.map((l) => l.liberado_em).filter((d): d is string => Boolean(d)).sort();
    return {
      chave,
      clienteId: primeira.client_id,
      nome: primeira.clients?.name?.trim() || "Cliente",
      email: primeira.clients?.email?.trim() || null,
      produtoId: primeira.product_id,
      produto: primeira.products?.name?.trim() || "Produto",
      imagem: primeira.products?.image_url ?? null,
      tipo: String(primeira.products?.digital_config?.tipo || "arquivos"),
      origem: linhas.some((l) => l.origem === "pedido") ? "pedido" : "cortesia",
      situacao: situacaoDoGrupo(linhas),
      liberadoEm: liberacoes[0] ?? null,
      expiraEm: prazos.length === linhas.length ? prazos[prazos.length - 1] : null,
      ultimoAcessoEm: acessos[acessos.length - 1] ?? null,
      downloads: linhas.reduce((soma, l) => soma + (Number(l.downloads) || 0), 0),
      itens: linhas.length,
      pedido: comPedido?.order_id ? { id: comPedido.order_id, numero: comPedido.orders?.number ?? null } : null,
      progresso: cursos.has(primeira.product_id)
        ? (() => {
            const total = Number(primeira.products?.digital_config?.curso?.conteudo?.aulas) || 0;
            return total ? Math.min(100, Math.round(((concluidas.get(chave) ?? 0) / total) * 100)) : 0;
          })()
        : null,
      certificado: certificados.get(chave) ?? null,
      lembreteEm: linhas.map((l) => l.lembrete_em).filter((d): d is string => Boolean(d)).sort().pop() ?? null,
    };
  });
}

/* Os acessos de uma pessoa num produto (o que as ações mexem). */
async function acessosDoAluno(admin: SupabaseClient, lojaId: string, clienteId: string, produtoId: string) {
  const { data, error } = await admin
    .from("digital_entitlements")
    .select("id,status,motivo,expira_em,deliverable_id,digital_deliverables(tipo,ativo,apagado_em,digital_files(status))")
    .eq("user_id", lojaId)
    .eq("client_id", clienteId)
    .eq("product_id", produtoId);
  if (error) throw new ErroDigital("Não foi possível ler os acessos.", 500);
  return (data ?? []) as unknown as Array<{
    id: string;
    status: string;
    motivo: string | null;
    expira_em: string | null;
    deliverable_id: string;
    digital_deliverables: { tipo: string; ativo: boolean; apagado_em: string | null; digital_files: { status: string } | null } | null;
  }>;
}

/* O estado de um acesso que volta a valer: o arquivo pode ainda estar chegando. */
function estadoAoReativar(acesso: { digital_deliverables: { tipo: string; digital_files: { status: string } | null } | null }) {
  const entregavel = acesso.digital_deliverables;
  if (entregavel?.tipo === "arquivo" && entregavel.digital_files?.status !== "pronto") return "aguardando_arquivo";
  if (entregavel?.tipo === "codigos") return "aguardando_codigo";
  return "ativo";
}

export async function mexerNoAluno(
  admin: SupabaseClient,
  params: { lojaId: string; clienteId: string; produtoId: string; acao: "bloquear" | "reativar" | "prazo"; prazoDias?: number | null }
): Promise<{ mexidos: number }> {
  const { lojaId, clienteId, produtoId, acao } = params;
  const acessos = await acessosDoAluno(admin, lojaId, clienteId, produtoId);
  if (!acessos.length) throw new ErroDigital("Esta pessoa não tem acesso a este produto.", 404);
  const agora = new Date().toISOString();
  let mexidos = 0;

  for (const acesso of acessos) {
    if (acesso.status === "revogado") continue;
    let mudanca: Record<string, unknown> | null = null;
    if (acao === "bloquear" && ["ativo", "aguardando_arquivo", "aguardando_codigo", "agendado", "suspenso"].includes(acesso.status)) {
      mudanca = { status: "pausado", motivo: "bloqueado pela loja" };
    } else if (acao === "reativar" && ["pausado", "suspenso", "expirado"].includes(acesso.status)) {
      const vencido = acesso.expira_em && new Date(acesso.expira_em).getTime() < Date.now();
      mudanca = { status: estadoAoReativar(acesso), motivo: null, ...(vencido ? { expira_em: null } : {}) };
    } else if (acao === "prazo") {
      const dias = params.prazoDias;
      const expira = typeof dias === "number" && dias > 0 ? new Date(Date.now() + Math.min(3650, Math.round(dias)) * 86400000).toISOString() : null;
      mudanca = { expira_em: expira, ...(acesso.status === "expirado" ? { status: estadoAoReativar(acesso), motivo: null } : {}) };
    }
    if (!mudanca) continue;
    const { error } = await admin
      .from("digital_entitlements")
      .update({ ...mudanca, atualizado_em: agora })
      .eq("id", acesso.id)
      .eq("user_id", lojaId);
    if (error) throw new ErroDigital("Não foi possível salvar. Tente de novo.", 500);
    mexidos += 1;
  }
  return { mexidos };
}

export type NovoAcesso = {
  cliente: { id: string } | { nome: string; email: string };
  produtoId: string;
  prazoDias: number | null;
};

/**
 * Acesso de cortesia: a pessoa (achada ou cadastrada) ganha os itens ativos do
 * produto. Quem já tem cortesia valendo só ganha o prazo novo.
 */
export async function darAcesso(
  admin: SupabaseClient,
  params: NovoAcesso & { lojaId: string; quem: string }
): Promise<{ clienteId: string; email: string | null; nome: string; criados: number; atualizados: number }> {
  const { lojaId, produtoId } = params;
  const { data: produto } = await admin
    .from("products")
    .select("id,name")
    .eq("id", produtoId)
    .eq("user_id", lojaId)
    .eq("product_kind", "digital")
    .maybeSingle();
  if (!produto) throw new ErroDigital("Produto não encontrado.", 404);

  const { data: itens } = await admin
    .from("digital_deliverables")
    .select("id,tipo,digital_files(status)")
    .eq("product_id", produtoId)
    .eq("user_id", lojaId)
    .eq("ativo", true)
    .is("apagado_em", null);
  const entregaveis = (itens ?? []) as unknown as Array<{ id: string; tipo: string; digital_files: { status: string } | null }>;
  if (!entregaveis.length) throw new ErroDigital("Este produto ainda não tem nada para entregar.", 409);

  /* A pessoa. */
  let cliente: { id: string; name: string | null; email: string | null } | null = null;
  if ("id" in params.cliente) {
    const { data } = await admin
      .from("clients")
      .select("id,name,email,is_deleted,is_system_placeholder")
      .eq("id", params.cliente.id)
      .eq("user_id", lojaId)
      .maybeSingle();
    const linha = data as { id: string; name: string | null; email: string | null; is_deleted: boolean | null; is_system_placeholder: boolean | null } | null;
    if (!linha || linha.is_deleted || linha.is_system_placeholder) throw new ErroDigital("Cliente não encontrado.", 404);
    cliente = linha;
  } else {
    const nome = params.cliente.nome.replace(/\s+/g, " ").trim().slice(0, 120);
    const email = params.cliente.email.trim().toLowerCase().slice(0, 254);
    if (nome.length < 2) throw new ErroDigital("Escreva o nome da pessoa.", 400);
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) throw new ErroDigital("Confira o e-mail: é por ele que a pessoa entra.", 400);
    const { data: achados } = await admin
      .from("clients")
      .select("id,name,email")
      .eq("user_id", lojaId)
      .ilike("email", email.replace(/[\\%_]/g, "\\$&"))
      .eq("is_deleted", false)
      .eq("is_system_placeholder", false)
      .limit(1);
    cliente = ((achados ?? []) as Array<{ id: string; name: string | null; email: string | null }>)[0] ?? null;
    if (!cliente) {
      const { data: criado, error } = await admin
        .from("clients")
        .insert({ user_id: lojaId, name: nome, email, type: "pf", origin: "cortesia_digital" })
        .select("id,name,email")
        .single();
      if (error || !criado) throw new ErroDigital("Não foi possível cadastrar a pessoa.", 500);
      cliente = criado as { id: string; name: string | null; email: string | null };
    }
  }

  const expira =
    typeof params.prazoDias === "number" && params.prazoDias > 0
      ? new Date(Date.now() + Math.min(3650, Math.round(params.prazoDias)) * 86400000).toISOString()
      : null;
  const agora = new Date().toISOString();

  const { data: existentes } = await admin
    .from("digital_entitlements")
    .select("id,deliverable_id")
    .eq("user_id", lojaId)
    .eq("client_id", cliente.id)
    .eq("origem", "cortesia")
    .not("status", "in", "(revogado,expirado)")
    .in(
      "deliverable_id",
      entregaveis.map((e) => e.id)
    );
  const jaTem = new Map(((existentes ?? []) as Array<{ id: string; deliverable_id: string }>).map((e) => [e.deliverable_id, e.id]));

  let criados = 0;
  let atualizados = 0;
  for (const entregavel of entregaveis) {
    const existente = jaTem.get(entregavel.id);
    if (existente) {
      await admin.from("digital_entitlements").update({ expira_em: expira, atualizado_em: agora }).eq("id", existente).eq("user_id", lojaId);
      atualizados += 1;
      continue;
    }
    const status =
      entregavel.tipo === "arquivo" && entregavel.digital_files?.status !== "pronto"
        ? "aguardando_arquivo"
        : entregavel.tipo === "codigos"
          ? "aguardando_codigo"
          : "ativo";
    const { error } = await admin.from("digital_entitlements").insert({
      user_id: lojaId,
      client_id: cliente.id,
      product_id: produtoId,
      deliverable_id: entregavel.id,
      origem: "cortesia",
      status,
      liberado_por: params.quem,
      liberado_em: agora,
      expira_em: expira,
    });
    if (error) throw new ErroDigital("Não foi possível dar o acesso. Tente de novo.", 500);
    criados += 1;
  }
  return { clienteId: cliente.id, email: cliente.email?.trim().toLowerCase() || null, nome: cliente.name || "", criados, atualizados };
}
