import type { PrismaClient } from "@prisma/client";
import type { FastifyInstance } from "fastify";
import { ProviderConnectionService } from "../../connections/providerConnections.js";
import type { LocalSecretEnvelope } from "../../security/secretEnvelope.js";
import { requireOrgRole, type GuardDeps } from "../auth/guards.js";
import { handleRouteError } from "../errors.js";
import { ProviderReadinessError, type ProviderReadinessProbe } from "../../connections/providerReadiness.js";

export interface ProviderConnectionRouteConfig {
  readonly prisma: PrismaClient;
  readonly sessionSecret: string;
  readonly secretEnvelope: LocalSecretEnvelope;
  readonly providerReadinessProbe?: ProviderReadinessProbe;
}

export async function registerProviderConnectionRoutes(
  app: FastifyInstance,
  config: ProviderConnectionRouteConfig,
): Promise<void> {
  const guardDeps: GuardDeps = { prisma: config.prisma, sessionSecret: config.sessionSecret };
  const connections = new ProviderConnectionService(
    config.prisma,
    config.secretEnvelope,
    config.providerReadinessProbe,
  );

  app.get("/api/org/:orgSlug/provider-connections", async (request, reply) => {
    try {
      const { orgSlug } = request.params as { orgSlug: string };
      const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug });
      if (auth === null) return;
      return {
        connections: await connections.list(auth.organization.id),
      };
    } catch (error) {
      request.log.error({
        requestId: request.id,
        operation: "provider_connection.list",
        ...providerFailureFacts(error),
      }, "provider connection read failed");
      return handleRouteError(reply, error);
    }
  });

  app.put("/api/org/:orgSlug/provider-connections/:providerId", async (request, reply) => {
    try {
      const { orgSlug, providerId } = request.params as { orgSlug: string; providerId: string };
      const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug });
      if (auth === null) return;
      const body = parseCredentialBody(request.body);
      const result = await connections.rotateByok({
        organizationId: auth.organization.id,
        providerId,
        actorUserId: auth.user.id,
        ...body,
      });
      request.log.info({
        organizationId: auth.organization.id,
        connectionId: result.id,
        providerId: result.providerId,
        mode: result.mode,
        status: result.status,
        secretVersion: result.activeVersion,
        keyId: result.keyId,
      }, result.created ? "provider connection created" : "provider connection rotated");
      return reply.status(result.created ? 201 : 200).send(withoutWriteResult(result));
    } catch (error) {
      request.log.error({
        requestId: request.id,
        operation: "provider_connection.rotate_byok",
        ...providerFailureFacts(error),
      }, "provider connection mutation failed");
      return handleRouteError(reply, error);
    }
  });
}

function providerFailureFacts(error: unknown): {
  readonly errorCode: string;
  readonly failureCategory: string;
  readonly upstreamStatus?: number;
} {
  if (error instanceof ProviderReadinessError) {
    return {
      errorCode: error.code,
      failureCategory: error.category,
      ...(error.upstreamStatus !== undefined ? { upstreamStatus: error.upstreamStatus } : {}),
    };
  }
  return {
    errorCode: "provider_connection_operation_failed",
    failureCategory: error instanceof Error ? error.name : typeof error,
  };
}

function parseCredentialBody(value: unknown): {
  readonly baseUrl: string;
  readonly authToken: string;
  readonly anthropicApiKey?: string;
} {
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
    throw new Error("invalid provider credential body");
  }
  const body = value as Record<string, unknown>;
  if (typeof body["baseUrl"] !== "string" || typeof body["authToken"] !== "string") {
    throw new Error("provider baseUrl and authToken are required");
  }
  const anthropicApiKey = body["anthropicApiKey"];
  if (anthropicApiKey !== undefined && typeof anthropicApiKey !== "string") {
    throw new Error("invalid anthropicApiKey");
  }
  return {
    baseUrl: body["baseUrl"],
    authToken: body["authToken"],
    ...(anthropicApiKey !== undefined ? { anthropicApiKey } : {}),
  };
}

function withoutWriteResult<T extends { readonly created: boolean }>(result: T): Omit<T, "created"> {
  const { created: _created, ...metadata } = result;
  return metadata;
}
