import { type NextRequest } from "next/server";
import { z } from "zod";
import {
  falhaDaEscritaDePlatformAdmin,
  requirePlatformAdmin,
} from "@/lib/auth/requirePlatformAdmin";
import { createAdminClient } from "@/lib/supabase/admin";
import { ok, fail } from "@/lib/api/wrappers";
import { audit } from "@/lib/audit";
import { randomUUID } from "node:crypto";
import { lerPaginasDeUso } from "@/lib/admin/usage-pages";

// ---------------------------------------------------------------------------
// Schemas
// ---------------------------------------------------------------------------

const querySchema = z.object({
  range: z.enum(["7d", "30d", "90d"]).default("30d"),
  tenant_id: z.string().uuid().optional(),
});

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

export interface UsageTenantRow {
  organization_id: string;
  tenant_name: string;
  tenant_slug: string;
  messages_count: number;
  ai_invocations_count: number;
  ai_tokens_total: number;
  ai_cost_cents: number;
  /** Chamadas cujo preço ainda não foi registrado; não equivalem a custo zero. */
  ai_unpriced_count?: number;
  conversations_count: number;
  /**
   * Nome do agente publicado nesta organização — o de maior prioridade, quando
   * há mais de um. `null` significa nenhum agente publicado, e é o sinal que o
   * operador procura na carteira: cliente cujo agente não está no ar.
   */
  agent_name: string | null;
}

export interface DailyPoint {
  date: string;
  count: number;
}

export interface DailyCostPoint {
  date: string;
  cents: number;
}

export interface DailyTokensPoint {
  date: string;
  tokens: number;
}

export interface UsageSeries {
  messages: DailyPoint[];
  ai_cost: DailyCostPoint[];
  ai_tokens: DailyTokensPoint[];
}

export interface UsageData {
  range: "7d" | "30d" | "90d";
  tenants: UsageTenantRow[];
  series: UsageSeries;
}

// ---------------------------------------------------------------------------
// GET /api/v1/admin/usage
// ---------------------------------------------------------------------------

export async function GET(req: NextRequest) {
  const requestId = randomUUID();

  let adminCtx: Awaited<ReturnType<typeof requirePlatformAdmin>>;
  try {
    adminCtx = await requirePlatformAdmin();
  } catch (err) {
    return falhaDaEscritaDePlatformAdmin(err, requestId);
  }

  const parsed = querySchema.safeParse(Object.fromEntries(req.nextUrl.searchParams.entries()));
  if (!parsed.success) {
    return fail("validation_error", "Invalid query params", 400, {
      requestId,
      details: parsed.error.flatten(),
    });
  }

  const { range, tenant_id } = parsed.data;
  const rangeMap: Record<string, number> = { "7d": 7, "30d": 30, "90d": 90 };
  const days = rangeMap[range] ?? 30;

  const admin = createAdminClient();

  // -------------------------------------------------------------------------
  // Per-tenant aggregates
  // -------------------------------------------------------------------------

  // Fetch organizations first (need name/slug)
  let orgsQuery = admin.from("organizations").select("id, display_name, slug");
  if (tenant_id) {
    orgsQuery = orgsQuery.eq("id", tenant_id);
  }
  const { data: orgs, error: orgsError } = await orgsQuery;
  if (orgsError) {
    return fail("db_error", "Failed to fetch organizations", 500, { requestId });
  }

  const orgMap = new Map(
    (orgs ?? []).map((o: { id: string; display_name: string; slug: string }) => [
      o.id,
      { display_name: o.display_name, slug: o.slug },
    ]),
  );

  const orgIds = (orgs ?? []).map((o: { id: string }) => o.id);

  // Pessoal não soma (spec 21, caminho 10): a mensagem do pessoal entra no
  // banco, mas o uso da plataforma conta operação — e pessoal saiu dela.
  // Marcados um a um, à mão: a lista é curta por construção.
  let pessoais: string[] = [];
  if (orgIds.length > 0) {
    const { data: marcados } = await admin
      .from("contacts")
      .select("id")
      .in("organization_id", orgIds)
      .eq("is_personal", true)
      .limit(2000);
    pessoais = ((marcados ?? []) as Array<{ id: string }>).map((c) => c.id);
  }

  // Dias de calendário UTC, incluindo hoje: tabela e gráficos medem [from,to).
  const endDate = new Date();
  endDate.setUTCHours(0, 0, 0, 0);
  endDate.setUTCDate(endDate.getUTCDate() + 1);
  const startDate = new Date(endDate);
  startDate.setUTCDate(startDate.getUTCDate() - days);
  const startIso = startDate.toISOString();
  const endIso = endDate.toISOString();
  interface UsoLinha {
    id: string;
    organization_id: string;
    created_at: string;
    input_tokens?: number | null;
    output_tokens?: number | null;
    cost_cents?: number | null;
  }
  const lerUso = (tabela: "messages" | "conversations" | "llm_calls", colunas: string) =>
    lerPaginasDeUso<UsoLinha>((depois, tamanho) => {
      let query = admin
        .from(tabela)
        .select(colunas)
        .in("organization_id", orgIds)
        .gte("created_at", startIso)
        .lt("created_at", endIso);
      if (tabela === "messages" && pessoais.length > 0) {
        query = query.not("contact_id", "in", `(${pessoais.join(",")})`);
      }
      if (depois !== null) query = query.gt("id", depois);
      return query.order("id", { ascending: true }).limit(tamanho) as unknown as PromiseLike<{
        data: UsoLinha[] | null;
        error: unknown;
      }>;
    });
  let mensagens: UsoLinha[] = [],
    conversas: UsoLinha[] = [],
    chamadas: UsoLinha[] = [];
  if (orgIds.length > 0) {
    try {
      [mensagens, conversas, chamadas] = await Promise.all([
        lerUso("messages", "id, organization_id, created_at"),
        lerUso("conversations", "id, organization_id, created_at"),
        lerUso(
          "llm_calls",
          "id, organization_id, created_at, input_tokens, output_tokens, cost_cents",
        ),
      ]);
    } catch {
      return fail("db_error", "Não foi possível ler o período completo de uso.", 500, {
        requestId,
      });
    }
  }
  const messagesCountMap = new Map<string, number>();
  const convsCountMap = new Map<string, number>();
  const aiInvCountMap = new Map<string, number>();
  const aiTokensMap = new Map<string, number>();
  const aiCostMap = new Map<string, number>();
  const aiUnpricedMap = new Map<string, number>();
  const msgDayMap = new Map<string, number>();
  const aiCostDayMap = new Map<string, number>();
  const aiTokensDayMap = new Map<string, number>();
  const somar = (mapa: Map<string, number>, chave: string, valor = 1) =>
    mapa.set(chave, (mapa.get(chave) ?? 0) + valor);
  for (const row of mensagens) {
    somar(messagesCountMap, row.organization_id);
    somar(msgDayMap, row.created_at.slice(0, 10));
  }
  for (const row of conversas) somar(convsCountMap, row.organization_id);
  for (const row of chamadas) {
    const tokens = (row.input_tokens ?? 0) + (row.output_tokens ?? 0);
    const dia = row.created_at.slice(0, 10);
    somar(aiInvCountMap, row.organization_id);
    somar(aiTokensMap, row.organization_id, tokens);
    somar(aiTokensDayMap, dia, tokens);
    somar(aiCostMap, row.organization_id, row.cost_cents ?? 0);
    if (row.cost_cents == null) somar(aiUnpricedMap, row.organization_id);
    somar(aiCostDayMap, dia, row.cost_cents ?? 0);
  }

  // ---- agente publicado por organização ----
  //
  // Uma linha por organização basta para a carteira: o agente de MAIOR
  // prioridade entre os publicados. A lista completa vive na aba Agente do
  // cliente — aqui a pergunta é só "este cliente está atendendo?".
  const agentNamePorOrg: Record<string, string> = {};
  if (orgIds.length > 0) {
    const { data: agentRows, error: agentErr } = await admin
      .from("ai_agents")
      .select("organization_id, name, priority")
      .in("organization_id", orgIds)
      .is("archived_at", null)
      .not("published_version_id", "is", null)
      .order("priority", { ascending: false });
    if (!agentErr && agentRows) {
      for (const row of agentRows) {
        const oid = row.organization_id as string;
        if (!agentNamePorOrg[oid]) agentNamePorOrg[oid] = row.name as string;
      }
    }
  }

  // Build tenant rows
  const tenants: UsageTenantRow[] = (orgs ?? [])
    .map((org: { id: string }) => {
      const meta = orgMap.get(org.id) ?? { display_name: org.id, slug: "" };
      return {
        organization_id: org.id,
        tenant_name: meta.display_name,
        tenant_slug: meta.slug,
        messages_count: messagesCountMap.get(org.id) ?? 0,
        ai_invocations_count: aiInvCountMap.get(org.id) ?? 0,
        ai_tokens_total: aiTokensMap.get(org.id) ?? 0,
        ai_cost_cents: aiCostMap.get(org.id) ?? 0,
        ai_unpriced_count: aiUnpricedMap.get(org.id) ?? 0,
        conversations_count: convsCountMap.get(org.id) ?? 0,
        agent_name: agentNamePorOrg[org.id] ?? null,
      };
    })
    .sort(
      (a: UsageTenantRow, b: UsageTenantRow) =>
        b.ai_cost_cents - a.ai_cost_cents || b.messages_count - a.messages_count,
    );

  // -------------------------------------------------------------------------
  // Daily series
  // -------------------------------------------------------------------------

  const dateLabels: string[] = [];
  for (let i = 0; i < days; i++) {
    const dia = new Date(startDate);
    dia.setUTCDate(dia.getUTCDate() + i);
    dateLabels.push(dia.toISOString().slice(0, 10));
  }

  const series: UsageSeries = {
    messages: dateLabels.map((date) => ({
      date,
      count: msgDayMap.get(date) ?? 0,
    })),
    ai_cost: dateLabels.map((date) => ({
      date,
      cents: aiCostDayMap.get(date) ?? 0,
    })),
    ai_tokens: dateLabels.map((date) => ({
      date,
      tokens: aiTokensDayMap.get(date) ?? 0,
    })),
  };

  // -------------------------------------------------------------------------
  // Audit
  // -------------------------------------------------------------------------

  void audit({
    action: "platform_admin.usage_viewed",
    actorUserId: adminCtx.user.id,
    actingAsPlatformAdmin: true,
    metadata: { range, tenant_id: tenant_id ?? null },
    requestId,
  });

  return ok<UsageData>(
    {
      range,
      tenants,
      series,
    },
    { requestId },
  );
}
