import { mudancasDaRemarcacao } from "@/lib/agenda/remarcar";
import { LEMBRETES_ACEITOS, validar } from "@/lib/agenda/validacao";

import type { ContextoDoAssistente } from "../contexto";
import { AVISO_DE_DADOS, dataBr, exigir, falha, hojeEmBrasilia, nomeDoCliente, pode, recusar } from "./comum";
import { semAcento } from "./operacao-regras";
import type { FerramentaDeEscrita, FerramentaDeLeitura } from "./tipos";

/*
 * Agenda: editar, remarcar, concluir, cancelar e excluir compromissos, e ver
 * as categorias. As regras são as mesmas das rotas do painel
 * (app/api/agenda/events/...): lib/agenda/validacao.ts e lib/agenda/remarcar.ts.
 */

type Compromisso = {
  id: string;
  title: string;
  description: string | null;
  event_date: string;
  start_time: string | null;
  end_time: string | null;
  client_id: string | null;
  responsible_user_id: string | null;
  status: string;
  recurrence: string;
  recurrence_until: string | null;
  location_address: string | null;
  location_lat: number | null;
  location_lng: number | null;
  category_id: string | null;
  color: string | null;
  lembrete_minutos: number | null;
};

const COLUNAS =
  "id,title,description,event_date,start_time,end_time,client_id,responsible_user_id,status,recurrence,recurrence_until,location_address,location_lat,location_lng,category_id,color,lembrete_minutos";

const SITUACAO: Record<string, string> = { agendado: "agendado", concluido: "concluído", cancelado: "cancelado" };

const CAMPO_DO_COMPROMISSO = {
  compromisso_id: { tipo: "id" as const, descricao: "Id do compromisso (o campo id de ver_agenda, nos itens do tipo compromisso).", obrigatorio: true },
};

async function acharCompromisso(ctx: ContextoDoAssistente, id: string): Promise<Compromisso> {
  const { data, error } = await ctx.supabase.from("agenda_events").select(COLUNAS).eq("id", id).eq("company_id", ctx.donoDosDados).maybeSingle();
  if (error) falha("ler o compromisso", error);
  if (!data) recusar("Compromisso não encontrado. Use ver_agenda para achar o id certo (só compromissos; entregas e contas não se editam por aqui).");
  return data as unknown as Compromisso;
}

const hora = (valor: string | null) => (valor ? String(valor).slice(0, 5) : null);

function horario(c: { start_time: string | null; end_time: string | null }) {
  const inicio = hora(c.start_time);
  const fim = hora(c.end_time);
  return inicio ? (fim ? `${inicio} às ${fim}` : inicio) : "dia inteiro";
}

function serie(c: Compromisso) {
  return c.recurrence && c.recurrence !== "none"
    ? ["Este compromisso se repete: a mudança vale para todas as repetições, não só para um dia."]
    : [];
}

/* ------------------------------------------------------ listar categorias */

export const listarCategoriasDaAgenda: FerramentaDeLeitura = {
  nome: "listar_categorias_da_agenda",
  titulo: "Ver as categorias da agenda",
  descricao:
    "Lista as categorias da agenda (visita, instalação, reunião...) com o id de cada uma. As de sistema (entregas, contas) não recebem compromissos; use uma das categorias da empresa em criar_compromisso ou editar_compromisso.",
  campos: {},
  tipo: "leitura",
  permissoes: ["agenda.read", "agenda.board.read"],
  async executar(ctx) {
    if (!pode(ctx, "agenda.read") && !pode(ctx, "agenda.board.read")) exigir(ctx, "agenda.board.read", "ver a agenda");
    const { data, error } = await ctx.supabase
      .from("agenda_categories")
      .select("id,name,color,is_system,sort_order")
      .eq("company_id", ctx.donoDosDados)
      .order("is_system", { ascending: false })
      .order("sort_order", { ascending: true })
      .order("name", { ascending: true });
    if (error) falha("ler as categorias da agenda", error);
    const linhas = (data ?? []) as Array<{ id: string; name: string | null; color: string | null; is_system: boolean | null }>;
    const categorias = linhas.map((c) => ({
      id: c.id,
      nome: c.name ?? "",
      cor: c.color,
      aceita_compromisso: c.is_system !== true,
    }));
    return {
      dados: { _observacao: AVISO_DE_DADOS, categorias, total: categorias.length },
      resumo: `Consultou as categorias da agenda (${categorias.length})`,
    };
  },
};

/* --------------------------------------------------------- editar compromisso */

type CargaDaEdicao = { id: string; dados: Record<string, unknown>; titulo: string; dia: string };

const APAGAVEIS = ["hora_inicio", "hora_fim", "descricao", "cliente", "endereco", "lembrete"] as const;

export const editarCompromisso: FerramentaDeEscrita<CargaDaEdicao> = {
  nome: "editar_compromisso",
  titulo: "Editar um compromisso",
  descricao:
    "Muda um compromisso da agenda: título, dia, horário, observações, cliente, endereço, lembrete, categoria ou situação. Mande só o que muda. Para tirar um horário, cliente, endereço, observação ou lembrete, use apagar. Para só trocar o dia, prefira remarcar_compromisso.",
  campos: {
    ...CAMPO_DO_COMPROMISSO,
    titulo: { tipo: "texto", descricao: "Novo título.", max: 200 },
    data: { tipo: "data", descricao: "Novo dia." },
    hora_inicio: { tipo: "hora", descricao: "Novo horário de início." },
    hora_fim: { tipo: "hora", descricao: "Novo horário de fim." },
    descricao: { tipo: "texto", descricao: "Novas observações (substituem as antigas).", max: 2000 },
    cliente_id: { tipo: "id", descricao: "Id do cliente ligado ao compromisso (de buscar_clientes)." },
    endereco: { tipo: "texto", descricao: "Endereço onde acontece.", max: 400 },
    lembrete_minutos: {
      tipo: "inteiro",
      descricao: "Avisar quantos minutos antes: 5, 10, 15, 30, 60, 120, 240, 1440 (um dia) ou 2880 (dois dias).",
      min: 5,
      max: 2880,
    },
    categoria: { tipo: "texto", descricao: "Nome ou id da categoria (de listar_categorias_da_agenda).", max: 80 },
    situacao: { tipo: "opcao", descricao: "agendado, concluido ou cancelado.", opcoes: ["agendado", "concluido", "cancelado"] },
    apagar: {
      tipo: "textos",
      descricao: `Campos a esvaziar: ${APAGAVEIS.join(", ")}.`,
      maxItens: 6,
      max: 20,
    },
  },
  tipo: "escrita",
  sobrescreve: true,
  permissoes: ["agenda.board.write"],
  async preparar(ctx, args) {
    exigir(ctx, "agenda.board.write", "editar compromissos");
    const atual = await acharCompromisso(ctx, String(args.compromisso_id));

    const apagar = new Set(((args.apagar as string[] | undefined) ?? []).map((nome) => nome.trim().toLowerCase()));
    for (const nome of apagar) {
      if (!(APAGAVEIS as readonly string[]).includes(nome)) recusar(`Não dá para apagar "${nome}". Use um destes: ${APAGAVEIS.join(", ")}.`);
    }
    if (args.lembrete_minutos !== undefined && !LEMBRETES_ACEITOS.has(Number(args.lembrete_minutos))) {
      recusar("Lembrete inválido. Use 5, 10, 15, 30, 60, 120, 240, 1440 ou 2880 minutos.");
    }

    let categoriaId = atual.category_id;
    let nomeDaCategoria: string | null = null;
    if (args.categoria) {
      const { data, error } = await ctx.supabase
        .from("agenda_categories")
        .select("id,name")
        .eq("company_id", ctx.donoDosDados)
        .eq("is_system", false);
      if (error) falha("conferir a categoria da agenda", error);
      const procurada = String(args.categoria);
      const lista = (data ?? []) as Array<{ id: string; name: string | null }>;
      const achada = lista.find((c) => c.id === procurada.toLowerCase()) ?? lista.find((c) => semAcento(c.name) === semAcento(procurada));
      if (!achada) {
        recusar(`Categoria não encontrada. As da empresa são: ${lista.map((c) => c.name).filter(Boolean).join(", ") || "nenhuma"}.`);
      }
      categoriaId = achada.id;
      nomeDaCategoria = achada.name;
    }

    let nomeDoClienteLigado: string | null = null;
    const clienteId = apagar.has("cliente") ? null : ((args.cliente_id as string | undefined) ?? atual.client_id);
    if (clienteId) {
      const { data: cliente } = await ctx.supabase
        .from("clients")
        .select("id,name,company_name")
        .eq("id", clienteId)
        .in("user_id", ctx.tenantIds)
        .maybeSingle();
      if (!cliente) recusar("Cliente não encontrado. Use buscar_clientes para achar o id certo.");
      nomeDoClienteLigado = nomeDoCliente(cliente as { name?: string | null; company_name?: string | null });
    }

    const escolher = (campo: (typeof APAGAVEIS)[number], novo: unknown, antigo: unknown) => (apagar.has(campo) ? null : novo !== undefined ? novo : antigo);

    /* A tela manda o compromisso inteiro na edição; aqui, o que não veio fica
       como estava, e o todo passa pelas mesmas regras. */
    const conferido = validar({
      title: args.titulo ?? atual.title,
      description: escolher("descricao", args.descricao, atual.description),
      event_date: args.data ?? atual.event_date,
      start_time: escolher("hora_inicio", args.hora_inicio, hora(atual.start_time)),
      end_time: escolher("hora_fim", args.hora_fim, hora(atual.end_time)),
      client_id: clienteId,
      responsible_user_id: atual.responsible_user_id,
      status: args.situacao ?? atual.status,
      recurrence: atual.recurrence,
      recurrence_until: atual.recurrence_until,
      location_address: escolher("endereco", args.endereco, atual.location_address),
      /* Endereço novo: a coordenada antiga apontaria para o lugar velho. */
      location_lat: args.endereco !== undefined || apagar.has("endereco") ? null : atual.location_lat,
      location_lng: args.endereco !== undefined || apagar.has("endereco") ? null : atual.location_lng,
      category_id: categoriaId,
      color: atual.color,
      lembrete_minutos: escolher("lembrete", args.lembrete_minutos, atual.lembrete_minutos),
    });
    if (!conferido.ok) recusar(conferido.erro);

    if (!nomeDaCategoria && categoriaId) {
      const { data } = await ctx.supabase.from("agenda_categories").select("name").eq("id", categoriaId).maybeSingle();
      nomeDaCategoria = (data as { name?: string | null } | null)?.name ?? null;
    }
    /* Mesma conferência da rota: a categoria tem de ser da empresa e não ser de sistema. */
    const { data: categoriaValida } = await ctx.supabase
      .from("agenda_categories")
      .select("id")
      .eq("id", conferido.dados.category_id)
      .eq("company_id", ctx.donoDosDados)
      .eq("is_system", false)
      .maybeSingle();
    if (!categoriaValida) recusar("A categoria deste compromisso não aceita compromissos. Escolha outra com o campo categoria.");

    const d = conferido.dados;
    return {
      previa: {
        o_que_vai_acontecer: "O compromisso será alterado.",
        antes: { titulo: atual.title, dia: atual.event_date, horario: horario(atual), situacao: SITUACAO[atual.status] ?? atual.status },
        depois: {
          titulo: d.title,
          dia: d.event_date,
          horario: horario(d),
          situacao: SITUACAO[d.status] ?? d.status,
          categoria: nomeDaCategoria,
          cliente: nomeDoClienteLigado,
          endereco: d.location_address,
          lembrete: d.lembrete_minutos ? `${d.lembrete_minutos} minutos antes` : "sem lembrete",
          observacoes: d.description,
        },
      },
      resumo: `Prévia da edição do compromisso ${d.title}`,
      avisos: serie(atual),
      carga: { id: atual.id, dados: d, titulo: d.title, dia: d.event_date },
    };
  },
  async gravar(ctx, carga) {
    const { error } = await ctx.supabase
      .from("agenda_events")
      .update({ ...carga.dados, updated_at: new Date().toISOString() })
      .eq("id", carga.id)
      .eq("company_id", ctx.donoDosDados);
    if (error) falha("salvar o compromisso", error);
    return {
      dados: { alterado: true, compromisso_id: carga.id, titulo: carga.titulo, dia: carga.dia },
      resumo: `Editou o compromisso "${carga.titulo}" (${dataBr(carga.dia)})`,
    };
  },
};

/* ------------------------------------------------------- remarcar compromisso */

type CargaDaRemarcacao = { id: string; titulo: string; mudancas: Record<string, unknown>; dia: string };

export const remarcarCompromisso: FerramentaDeEscrita<CargaDaRemarcacao> = {
  nome: "remarcar_compromisso",
  titulo: "Remarcar um compromisso",
  descricao:
    "Passa um compromisso para outro dia e, se quiser, outro horário de início (o fim anda junto, mantendo a duração). Compromisso agendado não vai para um dia que já passou.",
  campos: {
    ...CAMPO_DO_COMPROMISSO,
    data: { tipo: "data", descricao: "O novo dia.", obrigatorio: true },
    hora_inicio: { tipo: "hora", descricao: "O novo horário de início. Sem isto, o horário fica o mesmo." },
  },
  tipo: "escrita",
  sobrescreve: true,
  permissoes: ["agenda.board.write"],
  async preparar(ctx, args) {
    exigir(ctx, "agenda.board.write", "remarcar compromissos");
    const atual = await acharCompromisso(ctx, String(args.compromisso_id));
    const dia = String(args.data);
    const plano = mudancasDaRemarcacao(atual, { dia, hoje: hojeEmBrasilia(), inicioNovo: (args.hora_inicio as string | undefined) ?? null });
    if (!plano.ok) recusar(plano.erro);
    /* A hora de gravação é a do momento de gravar, não a da prévia. */
    const { updated_at: _quando, ...mudancas } = plano.mudancas;
    void _quando;
    const depois = {
      start_time: (mudancas.start_time as string | undefined) ?? atual.start_time,
      end_time: (mudancas.end_time as string | undefined) ?? atual.end_time,
    };
    return {
      previa: {
        o_que_vai_acontecer: "O compromisso será remarcado.",
        compromisso: atual.title,
        de: { dia: atual.event_date, horario: horario(atual) },
        para: { dia, horario: horario(depois) },
      },
      resumo: `Prévia: remarcar "${atual.title}" para ${dataBr(dia)}`,
      avisos: serie(atual),
      carga: { id: atual.id, titulo: atual.title, mudancas, dia },
    };
  },
  async gravar(ctx, carga) {
    const { error } = await ctx.supabase
      .from("agenda_events")
      .update({ ...carga.mudancas, updated_at: new Date().toISOString() })
      .eq("id", carga.id)
      .eq("company_id", ctx.donoDosDados);
    if (error) falha("remarcar o compromisso", error);
    return {
      dados: { remarcado: true, compromisso_id: carga.id, titulo: carga.titulo, dia: carga.dia },
      resumo: `Remarcou "${carga.titulo}" para ${dataBr(carga.dia)}`,
    };
  },
};

/* ------------------------------------------------- concluir e cancelar */

type CargaDaSituacao = { id: string; titulo: string; dia: string };

function mudarSituacao(nova: "concluido" | "cancelado"): FerramentaDeEscrita<CargaDaSituacao> {
  const concluir = nova === "concluido";
  return {
    nome: concluir ? "concluir_compromisso" : "cancelar_compromisso",
    titulo: concluir ? "Concluir um compromisso" : "Cancelar um compromisso",
    descricao: concluir
      ? "Marca um compromisso como concluído (já aconteceu). Não dá para concluir antes do dia marcado."
      : "Marca um compromisso como cancelado. Ele continua na agenda, riscado, e pode voltar a agendado com editar_compromisso. Para apagar de vez, use excluir_compromisso.",
    campos: { ...CAMPO_DO_COMPROMISSO },
    tipo: "escrita",
    permissoes: ["agenda.board.write"],
    async preparar(ctx, args) {
      exigir(ctx, "agenda.board.write", concluir ? "concluir compromissos" : "cancelar compromissos");
      const atual = await acharCompromisso(ctx, String(args.compromisso_id));
      if (atual.status === nova) recusar(`Este compromisso já está ${SITUACAO[nova]}.`);
      if (concluir && String(atual.event_date) > hojeEmBrasilia()) {
        recusar("Esse compromisso ainda não aconteceu — não dá pra concluir antes da data.");
      }
      return {
        previa: {
          o_que_vai_acontecer: concluir ? "O compromisso será marcado como concluído." : "O compromisso será marcado como cancelado.",
          compromisso: atual.title,
          dia: atual.event_date,
          horario: horario(atual),
          situacao_atual: SITUACAO[atual.status] ?? atual.status,
        },
        resumo: `Prévia: ${concluir ? "concluir" : "cancelar"} "${atual.title}"`,
        avisos: serie(atual),
        carga: { id: atual.id, titulo: atual.title, dia: atual.event_date },
      };
    },
    async gravar(ctx, carga) {
      const { error } = await ctx.supabase
        .from("agenda_events")
        .update({ status: nova, updated_at: new Date().toISOString() })
        .eq("id", carga.id)
        .eq("company_id", ctx.donoDosDados);
      if (error) falha(concluir ? "concluir o compromisso" : "cancelar o compromisso", error);
      return {
        dados: { compromisso_id: carga.id, titulo: carga.titulo, situacao: SITUACAO[nova] },
        resumo: `${concluir ? "Concluiu" : "Cancelou"} o compromisso "${carga.titulo}" (${dataBr(carga.dia)})`,
      };
    },
  };
}

export const concluirCompromisso = mudarSituacao("concluido");
export const cancelarCompromisso = mudarSituacao("cancelado");

/* ------------------------------------------------------- excluir compromisso */

export const excluirCompromisso: FerramentaDeEscrita<CargaDaSituacao> = {
  nome: "excluir_compromisso",
  titulo: "Excluir um compromisso",
  descricao:
    "Apaga um compromisso da agenda de vez (com todas as repetições, se ele se repete). Não tem volta. Se a ideia é só registrar que não vai acontecer, prefira cancelar_compromisso.",
  campos: { ...CAMPO_DO_COMPROMISSO },
  tipo: "escrita",
  sobrescreve: true,
  sensivel: true,
  permissoes: ["agenda.board.write"],
  async preparar(ctx, args) {
    exigir(ctx, "agenda.board.write", "excluir compromissos");
    const atual = await acharCompromisso(ctx, String(args.compromisso_id));
    return {
      previa: {
        o_que_vai_acontecer: "O compromisso será apagado da agenda. Não tem volta.",
        compromisso: atual.title,
        dia: atual.event_date,
        horario: horario(atual),
        situacao: SITUACAO[atual.status] ?? atual.status,
      },
      resumo: `Prévia: excluir o compromisso "${atual.title}"`,
      avisos: serie(atual),
      carga: { id: atual.id, titulo: atual.title, dia: atual.event_date },
    };
  },
  async gravar(ctx, carga) {
    const { error } = await ctx.supabase.from("agenda_events").delete().eq("id", carga.id).eq("company_id", ctx.donoDosDados);
    if (error) falha("apagar o compromisso", error);
    return {
      dados: { excluido: true, compromisso_id: carga.id, titulo: carga.titulo },
      resumo: `Excluiu o compromisso "${carga.titulo}" (${dataBr(carga.dia)})`,
    };
  },
};

export const FERRAMENTAS_DA_AGENDA = [
  listarCategoriasDaAgenda,
  editarCompromisso,
  remarcarCompromisso,
  concluirCompromisso,
  cancelarCompromisso,
  excluirCompromisso,
];
