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

import { digitaisLiberadosAgora } from "@/lib/digitais/liberacao-do-banco";
import { limitesDaLoja } from "@/lib/digitais/limites";
import { tamanhoLegivel } from "@/lib/digitais/nome-do-arquivo";
import {
  abrirEnvioDeArquivo,
  apagarArquivoDaBiblioteca,
  concluirEnvioDeArquivo,
  retomarEnvioDeArquivo,
} from "@/lib/digitais/servidor/acoes/arquivos";
import { ErroDigital } from "@/lib/digitais/servidor/contexto";
import { assinaturaDaEmpresa } from "@/lib/fiscal/servidor/assinatura-da-empresa";

/*
 * Vídeo (ou arquivo) que a pessoa manda ao assistente para os produtos
 * digitais. Os assistentes não repassam anexos; então `pedir_video` cria uma
 * linha em assistentes_envios (migration 192) com o tipo "digital:video" (ou
 * "digital:arquivo") e a pessoa envia pela página /enviar/<código>, sem
 * login: o código manda, como no envio de imagem.
 *
 * O arquivo NÃO passa pela VPS: vai do navegador direto para o depósito dos
 * digitais (R2), em partes, pelas MESMAS funções da biblioteca do painel
 * (lib/digitais/servidor/acoes/arquivos.ts): formato, espaço do plano,
 * tamanho por arquivo e o conteúdo real conferidos do mesmo jeito. No fim,
 * o arquivo está na biblioteca ("Arquivos e espaço") e a linha do envio
 * guarda "digital_file:<id>" em `caminho` (a faxina dos envios só apaga o
 * que fica em "<empresa>/entrada/", então nunca mexe nele).
 */

export const TIPO_DO_ENVIO_DE_VIDEO = "digital:video";
export const TIPO_DO_ENVIO_DE_ARQUIVO = "digital:arquivo";
export const CAMINHO_DO_ARQUIVO_DIGITAL = /^digital_file:([0-9a-f-]{36})$/i;

export function ehEnvioDigital(tipo: string | null | undefined): boolean {
  return typeof tipo === "string" && tipo.startsWith("digital:");
}

export function arquivoDoCaminho(caminho: string | null | undefined): string | null {
  const achado = CAMINHO_DO_ARQUIVO_DIGITAL.exec(String(caminho ?? ""));
  return achado ? achado[1].toLowerCase() : null;
}

export type EnvioDigital = {
  codigo: string;
  companyId: string;
  usuarioId: string | null;
  tipo: string;
  paraQue: string | null;
  caminho: string | null;
  nome: string | null;
  tamanho: number | null;
  recebidoEm: string | null;
  venceEm: string;
};

type Linha = {
  codigo: string;
  company_id: string;
  usuario_id: string | null;
  tipo: string | null;
  para_que: string | null;
  caminho: string | null;
  nome: string | null;
  tamanho: number | null;
  recebido_em: string | null;
  vence_em: string;
};

/** O envio deste código, se for de vídeo ou arquivo digital e ainda valer. */
export async function lerEnvioDigital(admin: SupabaseClient, codigo: string): Promise<EnvioDigital> {
  const { data, error } = await admin
    .from("assistentes_envios")
    .select("codigo,company_id,usuario_id,tipo,para_que,caminho,nome,tamanho,recebido_em,vence_em")
    .eq("codigo", codigo)
    .maybeSingle();
  if (error) throw new ErroDigital("Não deu para abrir o envio agora. Tente de novo em instantes.", 503);
  const linha = data as Linha | null;
  if (!linha || !ehEnvioDigital(linha.tipo)) throw new ErroDigital("Este link de envio não existe. Peça um link novo ao assistente.", 404);
  if (new Date(linha.vence_em).getTime() <= Date.now()) throw new ErroDigital("Este link venceu. Peça um link novo ao assistente.", 410);
  return {
    codigo: linha.codigo,
    companyId: linha.company_id,
    usuarioId: linha.usuario_id,
    tipo: String(linha.tipo),
    paraQue: linha.para_que,
    caminho: linha.caminho,
    nome: linha.nome,
    tamanho: linha.tamanho,
    recebidoEm: linha.recebido_em,
    venceEm: linha.vence_em,
  };
}

/* A loja da empresa (o dono), a assinatura e a liberação: as mesmas regras do painel. */
async function lojaDoEnvio(admin: SupabaseClient, envio: EnvioDigital) {
  const { data } = await admin.from("companies").select("owner_id").eq("id", envio.companyId).maybeSingle();
  const dono = (data as { owner_id?: string | null } | null)?.owner_id ?? null;
  const lojaId = dono ?? envio.companyId;
  if (!(await digitaisLiberadosAgora([envio.companyId, dono]))) {
    throw new ErroDigital("A área de produtos digitais não está liberada para esta loja.", 403);
  }
  const assinatura = await assinaturaDaEmpresa(admin, lojaId);
  if (!assinatura.ativa) throw new ErroDigital("A assinatura do Ktalogu desta loja está inativa. O envio não pode ser feito agora.", 402);
  return { lojaId, situacao: assinatura.situacao };
}

/** O que a página mostra antes de enviar: os limites do plano. */
export async function limitesDoEnvio(admin: SupabaseClient, envio: EnvioDigital) {
  const { situacao } = await lojaDoEnvio(admin, envio);
  const limites = limitesDaLoja(situacao);
  return { porArquivo: limites.porArquivo, porArquivoLegivel: tamanhoLegivel(limites.porArquivo) };
}

/** Abre o envio (as partes vão direto para o depósito). Um envio novo pelo mesmo link troca o anterior que não terminou. */
export async function abrirEnvioDigital(admin: SupabaseClient, envio: EnvioDigital, corpo: unknown) {
  const { lojaId, situacao } = await lojaDoEnvio(admin, envio);
  const aberto = await abrirEnvioDeArquivo(
    { admin, lojaId, situacao, liberado: true, criadoPor: envio.usuarioId ?? lojaId },
    corpo,
    { soVideo: envio.tipo === TIPO_DO_ENVIO_DE_VIDEO }
  );
  /* O envio novo abriu: o anterior que não chegou ao fim sai (cancelado no
     depósito). O que já chegou fica na biblioteca. Um envio recusado na
     conferência acima não mexe no anterior. */
  const anterior = arquivoDoCaminho(envio.caminho);
  if (anterior && !envio.recebidoEm) await apagarArquivoDaBiblioteca(admin, lojaId, anterior).catch(() => undefined);
  const nome = typeof (corpo as { nome?: unknown } | null)?.nome === "string" ? String((corpo as { nome: string }).nome).slice(0, 200) : null;
  await admin
    .from("assistentes_envios")
    .update({ caminho: `digital_file:${aberto.id}`, nome, tamanho: null, recebido_em: null })
    .eq("codigo", envio.codigo)
    .eq("company_id", envio.companyId);
  return aberto;
}

function arquivoDoEnvio(envio: EnvioDigital, pedido: string | null) {
  const atual = arquivoDoCaminho(envio.caminho);
  /* Só o arquivo que ESTE link abriu: o id que o navegador manda tem de ser o mesmo. */
  if (!atual || (pedido && pedido.toLowerCase() !== atual)) throw new ErroDigital("Este envio não foi encontrado. Escolha o arquivo de novo.", 404);
  return atual;
}

export async function retomarEnvioDigital(admin: SupabaseClient, envio: EnvioDigital, arquivoId: string | null) {
  const { lojaId } = await lojaDoEnvio(admin, envio);
  return retomarEnvioDeArquivo(admin, lojaId, arquivoDoEnvio(envio, arquivoId));
}

export async function concluirEnvioDigital(admin: SupabaseClient, envio: EnvioDigital, arquivoId: string | null, corpo: unknown, request: Request) {
  const { lojaId } = await lojaDoEnvio(admin, envio);
  const id = arquivoDoEnvio(envio, arquivoId);
  const resultado = await concluirEnvioDeArquivo(admin, lojaId, id, corpo, request);
  const { data } = await admin.from("digital_files").select("nome,tamanho_bytes,mime").eq("id", id).eq("user_id", lojaId).maybeSingle();
  const arquivo = data as { nome: string; tamanho_bytes: number; mime: string | null } | null;
  await admin
    .from("assistentes_envios")
    .update({
      nome: arquivo?.nome ?? envio.nome,
      tamanho: arquivo ? Math.min(2_000_000_000, Number(arquivo.tamanho_bytes) || 0) : null,
      recebido_em: new Date().toISOString(),
    })
    .eq("codigo", envio.codigo)
    .eq("company_id", envio.companyId);
  return resultado;
}

export async function cancelarEnvioDigital(admin: SupabaseClient, envio: EnvioDigital, arquivoId: string | null) {
  const { lojaId } = await lojaDoEnvio(admin, envio);
  const id = arquivoDoEnvio(envio, arquivoId);
  if (envio.recebidoEm) return { ok: true };
  return apagarArquivoDaBiblioteca(admin, lojaId, id);
}
