import type { FastifyInstance } from "fastify";
import { z } from "zod";
import { requireSession } from "../../lib/auth.js";
import { writeAuditLog } from "../../lib/audit.js";
import { encryptSecret } from "../../lib/crypto.js";
import { createId } from "../../lib/id.js";
import { prisma } from "../../lib/prisma.js";
import { getSessionProfile } from "../../lib/session.js";
import { listCatalogCustomAgents } from "../../lib/custom-agents.js";
import { listFormEngineAgents } from "../../lib/form-engine.js";
import {
  getSystemAgentDefinitions,
  resolveSystemAgentInstructions,
  upsertSystemAgentInstructions,
} from "../../lib/system-agents.js";

const aiCredentialSchema = z.object({
  provider: z.string().min(2).max(50).default("openai"),
  credentialName: z.string().min(2).max(100),
  apiKey: z.string().min(20).max(400),
  defaultModel: z.string().min(2).max(100).default("gpt-5.4"),
  rateLimitPerMinute: z.coerce.number().int().positive().optional(),
  monthlyBudget: z.coerce.number().positive().optional(),
  alertThreshold: z.coerce.number().positive().max(100).optional(),
  setAsDefault: z.boolean().default(true),
});

const agentScopeSchema = z.object({
  scope: z.enum(["system_agent", "team_agent", "form_agent", "custom_agent"]),
  agentId: z.string().min(2).max(120),
});

const agentInstructionsSchema = z.object({
  instructions: z.string().min(10).max(40000),
});

function buildInternalProfileName(displayName: string) {
  return `Perfil interno • ${displayName}`;
}

export async function registerAiRoutes(app: FastifyInstance) {
  app.get("/settings", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);

    if (!profile) {
      throw reply.unauthorized("Session is no longer valid");
    }

    const [settings, credentials] = await prisma.$transaction([
      prisma.lawFirmAiSetting.findUnique({
        where: {
          law_firm_id: profile.lawFirm.id,
        },
      }),
      prisma.lawFirmAiCredential.findMany({
        where: {
          law_firm_id: profile.lawFirm.id,
        },
        orderBy: {
          created_at: "desc",
        },
      }),
    ]);

    return {
      settings,
      credentials: credentials.map((credential) => ({
        id: credential.id,
        provider: credential.provider,
        credentialName: credential.credential_name,
        keyLast4: credential.key_last4,
        isActive: credential.is_active,
        isDefault: credential.is_default,
        createdAt: credential.created_at,
      })),
    };
  });

  app.post("/credentials", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);

    if (!profile) {
      throw reply.unauthorized("Session is no longer valid");
    }

    const payload = aiCredentialSchema.parse(request.body);
    const encryptedApiKey = encryptSecret(payload.apiKey);
    const keyLast4 = payload.apiKey.slice(-4);
    const credentialId = createId();

    const result = await prisma.$transaction(async (tx) => {
      if (payload.setAsDefault) {
        await tx.lawFirmAiCredential.updateMany({
          where: {
            law_firm_id: profile.lawFirm.id,
            provider: payload.provider,
          },
          data: {
            is_default: false,
          },
        });
      }

      const credential = await tx.lawFirmAiCredential.create({
        data: {
          id: credentialId,
          law_firm_id: profile.lawFirm.id,
          provider: payload.provider,
          credential_name: payload.credentialName,
          encrypted_api_key: encryptedApiKey,
          encryption_key_version: "v1",
          key_last4: keyLast4,
          is_active: true,
          is_default: payload.setAsDefault,
          created_by_user_id: profile.user.id,
        },
      });

      const settings = await tx.lawFirmAiSetting.upsert({
        where: {
          law_firm_id: profile.lawFirm.id,
        },
        update: {
          default_provider: payload.provider,
          default_model: payload.defaultModel,
          active_credential_id: payload.setAsDefault ? credentialId : undefined,
          rate_limit_per_minute: payload.rateLimitPerMinute,
          monthly_budget: payload.monthlyBudget,
          alert_threshold: payload.alertThreshold,
          is_active: true,
        },
        create: {
          id: createId(),
          law_firm_id: profile.lawFirm.id,
          default_provider: payload.provider,
          default_model: payload.defaultModel,
          active_credential_id: payload.setAsDefault ? credentialId : null,
          rate_limit_per_minute: payload.rateLimitPerMinute,
          monthly_budget: payload.monthlyBudget,
          alert_threshold: payload.alertThreshold,
          is_active: true,
        },
      });

      return { credential, settings };
    });

    await writeAuditLog({
      lawFirmId: profile.lawFirm.id,
      officeId: profile.user.primaryOfficeId ?? null,
      actorUserId: profile.user.id,
      entityType: "law_firm_ai_credential",
      entityId: result.credential.id,
      action: "ai.credential.create",
      afterJson: {
        provider: result.credential.provider,
        credentialName: result.credential.credential_name,
        keyLast4: result.credential.key_last4,
        isDefault: result.credential.is_default,
      },
      request,
    });

    return reply.code(201).send({
      id: result.credential.id,
      provider: result.credential.provider,
      credentialName: result.credential.credential_name,
      keyLast4: result.credential.key_last4,
      isActive: result.credential.is_active,
      isDefault: result.credential.is_default,
      defaultModel: result.settings.default_model,
    });
  });

  app.get("/agents/catalog", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);

    if (!profile) {
      throw reply.unauthorized("Session is no longer valid");
    }

    const [teamAgents, formAgents, customAgents] = await Promise.all([
      prisma.$queryRaw<
        Array<{
          id: string;
          user_id: string;
          agent_name: string;
          system_prompt: string | null;
          updated_at: Date;
          team_name: string | null;
          email: string;
          display_name: string | null;
          first_name: string | null;
          last_name: string | null;
        }>
      >`
        SELECT
          ta.id,
          ta.user_id,
          ta.agent_name,
          ta.system_prompt,
          ta.updated_at,
          t.name AS team_name,
          u.email,
          u.display_name,
          u.first_name,
          u.last_name
        FROM team_agents ta
        INNER JOIN teams t ON t.id = ta.team_id
        INNER JOIN users u ON u.id = ta.user_id
        WHERE ta.law_firm_id = ${profile.lawFirm.id}
          AND ta.id = (
            SELECT ta2.id
            FROM team_agents ta2
            WHERE ta2.law_firm_id = ${profile.lawFirm.id}
              AND ta2.user_id = ta.user_id
            ORDER BY ta2.created_at ASC, ta2.id ASC
            LIMIT 1
          )
        ORDER BY ta.agent_name ASC
      `,
      listFormEngineAgents({
        lawFirmId: profile.lawFirm.id,
        actorUserId: profile.user.id,
      }),
      listCatalogCustomAgents({
        lawFirmId: profile.lawFirm.id,
      }),
    ]);

    const systemAgents = await Promise.all(
      getSystemAgentDefinitions({
        lawFirmName: profile.lawFirm.name,
      }).map(async (definition) => {
        const resolved = await resolveSystemAgentInstructions({
          lawFirmId: profile.lawFirm.id,
          lawFirmName: profile.lawFirm.name,
          agentCode: definition.agentCode,
        });

        return {
          scope: "system_agent" as const,
          id: definition.agentCode,
          agentCode: definition.agentCode,
          name: definition.name,
          description: definition.description,
          instructions: resolved.instructions,
          updatedAt: resolved.updatedAt?.toISOString() ?? null,
          contextLabel: "System",
          badges: [resolved.isCustomized ? "customized" : "default"],
        };
      }),
    );

    const normalizedTeamAgents = teamAgents.map((agent) => {
      const userDisplayName =
        agent.display_name?.trim() ||
        `${agent.first_name ?? ""} ${agent.last_name ?? ""}`.trim() ||
        agent.email;
      const normalizedInstructions = String(agent.system_prompt ?? "").trim();

      return {
        scope: "team_agent" as const,
        id: agent.id,
        agentCode: `team_agent:${agent.id}`,
        name: buildInternalProfileName(userDisplayName),
        description: `Internal memory profile for ${userDisplayName}. This is not a generic client-facing agent.`,
        instructions: normalizedInstructions,
        updatedAt: agent.updated_at.toISOString(),
        contextLabel: `${agent.team_name ?? "Team"} • ${userDisplayName}`,
        badges: ["internal_profile", normalizedInstructions ? "customized" : "standard"],
      };
    });

    const normalizedFormAgents = formAgents.map((agent) => ({
      scope: "form_agent" as const,
      id: agent.templateId,
      agentCode: agent.agentCode,
      name: agent.formName,
      description:
        agent.description ||
        `Form agent for ${agent.formName}${agent.versionLabel ? ` (${agent.versionLabel})` : ""}.`,
      instructions: agent.prompt,
      updatedAt: null,
      contextLabel: `${agent.formCode} • ${agent.fieldCount} field(s)`,
      badges: [agent.usesSpecializedLoop ? "specialized" : "form"],
    }));

    const normalizedCustomAgents = customAgents.map((agent) => ({
      scope: "custom_agent" as const,
      id: agent.id,
      agentCode: `custom_agent:${agent.id}`,
      name: agent.agent_name,
      description:
        agent.description?.trim() ||
        `Custom workspace agent configured for ${agent.team_name}.`,
      instructions: agent.system_prompt,
      updatedAt: agent.updated_at.toISOString(),
      contextLabel: `${agent.team_name} • Custom agent`,
      badges: ["custom", "team"],
    }));

    return [...systemAgents, ...normalizedCustomAgents, ...normalizedTeamAgents, ...normalizedFormAgents].sort((left, right) => {
      const scopeOrder = {
        system_agent: 0,
        custom_agent: 1,
        team_agent: 2,
        form_agent: 3,
      } as const;

      const scopeDifference = scopeOrder[left.scope] - scopeOrder[right.scope];
      if (scopeDifference !== 0) {
        return scopeDifference;
      }

      return left.name.localeCompare(right.name);
    });
  });

  app.patch("/agents/:scope/:agentId", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);
    const { scope, agentId } = agentScopeSchema.parse(request.params);
    const payload = agentInstructionsSchema.parse(request.body);

    if (!profile) {
      throw reply.unauthorized("Session is no longer valid");
    }

    const normalizedInstructions = payload.instructions.trim();

    if (scope === "system_agent") {
      const definition = getSystemAgentDefinitions({
        lawFirmName: profile.lawFirm.name,
      }).find((item) => item.agentCode === agentId);

      if (!definition) {
        throw reply.notFound("System agent not found");
      }

      await upsertSystemAgentInstructions({
        lawFirmId: profile.lawFirm.id,
        actorUserId: profile.user.id,
        agentCode: definition.agentCode,
        instructions: normalizedInstructions,
      });
    }

    if (scope === "team_agent") {
      const updatedCount = await prisma.$executeRaw`
        UPDATE team_agents
        SET
          system_prompt = ${normalizedInstructions},
          updated_at = CURRENT_TIMESTAMP
        WHERE law_firm_id = ${profile.lawFirm.id}
          AND id = ${agentId}
      `;

      if (!Number(updatedCount)) {
        throw reply.notFound("Team agent not found");
      }
    }

    if (scope === "form_agent") {
      const updatedCount = await prisma.$executeRaw`
        UPDATE form_templates
        SET
          agent_prompt = ${normalizedInstructions},
          updated_at = CURRENT_TIMESTAMP
        WHERE law_firm_id = ${profile.lawFirm.id}
          AND id = ${agentId}
      `;

      if (!Number(updatedCount)) {
        throw reply.notFound("Form agent not found");
      }
    }

    if (scope === "custom_agent") {
      const updatedCount = await prisma.$executeRaw`
        UPDATE custom_agents
        SET
          system_prompt = ${normalizedInstructions},
          updated_by_user_id = ${profile.user.id},
          updated_at = CURRENT_TIMESTAMP
        WHERE law_firm_id = ${profile.lawFirm.id}
          AND id = ${agentId}
          AND archived_at IS NULL
      `;

      if (!Number(updatedCount)) {
        throw reply.notFound("Custom agent not found");
      }
    }

    await writeAuditLog({
      lawFirmId: profile.lawFirm.id,
      officeId: profile.user.primaryOfficeId ?? null,
      actorUserId: profile.user.id,
      entityType: "agent_catalog",
      entityId: `${scope}:${agentId}`,
      action: "ai.agent.instructions.update",
      afterJson: {
        scope,
        agentId,
        instructionsLength: normalizedInstructions.length,
      },
      request,
    });

    return {
      ok: true,
      scope,
      agentId,
    };
  });
}
