import { Router } from "express";
import { AGENT_ADAPTER_TYPES, createEnvironmentSchema, getEnvironmentCapabilities, probeEnvironmentConfigSchema, updateEnvironmentSchema, } from "@paperclipai/shared";
import { conflict, forbidden } from "../errors.js";
import { validate } from "../middleware/validate.js";
import { accessService, agentService, issueService, logActivity, projectService, } from "../services/index.js";
import { collectEnvironmentSecretRefs, normalizeEnvironmentConfigForPersistence, normalizeEnvironmentConfigForProbe, readSshEnvironmentPrivateKeySecretId, } from "../services/environment-config.js";
import { probeEnvironment } from "../services/environment-probe.js";
import { secretService } from "../services/secrets.js";
import { listReadyPluginEnvironmentDrivers } from "../services/plugin-environment-driver.js";
import { getConfiguredSecretProvider } from "../secrets/configured-provider.js";
import { assertCompanyAccess, getActorInfo } from "./authz.js";
import { environmentService } from "../services/environments.js";
import { executionWorkspaceService } from "../services/execution-workspaces.js";
export function environmentRoutes(db, options = {}) {
    const router = Router();
    const agents = agentService(db);
    const access = accessService(db);
    const svc = environmentService(db);
    const executionWorkspaces = executionWorkspaceService(db);
    const issues = issueService(db);
    const projects = projectService(db);
    const secrets = secretService(db);
    function parseObject(value) {
        return value && typeof value === "object" && !Array.isArray(value)
            ? value
            : {};
    }
    function canCreateAgents(agent) {
        if (!agent.permissions || typeof agent.permissions !== "object")
            return false;
        return Boolean(agent.permissions.canCreateAgents);
    }
    async function assertCanMutateEnvironments(req, companyId) {
        assertCompanyAccess(req, companyId);
        if (req.actor.type === "board") {
            if (req.actor.source === "local_implicit" || req.actor.isInstanceAdmin)
                return;
            const allowed = await access.canUser(companyId, req.actor.userId, "environments:manage");
            if (!allowed) {
                throw forbidden("Missing permission: environments:manage");
            }
            return;
        }
        if (!req.actor.agentId) {
            throw forbidden("Agent authentication required");
        }
        const actorAgent = await agents.getById(req.actor.agentId);
        if (!actorAgent || actorAgent.companyId !== companyId) {
            throw forbidden("Agent key cannot access another company");
        }
        const allowedByGrant = await access.hasPermission(companyId, "agent", actorAgent.id, "environments:manage");
        if (allowedByGrant || canCreateAgents(actorAgent)) {
            return;
        }
        throw forbidden("Missing permission: environments:manage");
    }
    async function actorCanReadEnvironmentConfigurations(req, companyId) {
        assertCompanyAccess(req, companyId);
        if (req.actor.type === "board") {
            if (req.actor.source === "local_implicit" || req.actor.isInstanceAdmin)
                return true;
            return access.canUser(companyId, req.actor.userId, "environments:manage");
        }
        if (!req.actor.agentId)
            return false;
        const actorAgent = await agents.getById(req.actor.agentId);
        if (!actorAgent || actorAgent.companyId !== companyId)
            return false;
        const allowedByGrant = await access.hasPermission(companyId, "agent", actorAgent.id, "environments:manage");
        return allowedByGrant || canCreateAgents(actorAgent);
    }
    function redactEnvironmentForRestrictedView(environment) {
        return {
            ...environment,
            config: {},
            metadata: null,
            configRedacted: true,
            metadataRedacted: true,
        };
    }
    function summarizeEnvironmentUpdate(patch, environment) {
        const details = {
            changedFields: Object.keys(patch).sort(),
        };
        if (patch.name !== undefined)
            details.name = environment.name;
        if (patch.driver !== undefined)
            details.driver = environment.driver;
        if (patch.status !== undefined)
            details.status = environment.status;
        if (patch.description !== undefined)
            details.descriptionChanged = true;
        if (patch.config !== undefined) {
            details.configChanged = true;
            details.configTopLevelKeyCount =
                patch.config && typeof patch.config === "object" && !Array.isArray(patch.config)
                    ? Object.keys(patch.config).length
                    : 0;
        }
        if (patch.metadata !== undefined) {
            details.metadataChanged = true;
            details.metadataTopLevelKeyCount =
                patch.metadata && typeof patch.metadata === "object" && !Array.isArray(patch.metadata)
                    ? Object.keys(patch.metadata).length
                    : 0;
        }
        return details;
    }
    router.get("/companies/:companyId/environments", async (req, res) => {
        const companyId = req.params.companyId;
        assertCompanyAccess(req, companyId);
        const rows = await svc.list(companyId, {
            status: req.query.status,
            driver: req.query.driver,
        });
        const canReadConfigs = await actorCanReadEnvironmentConfigurations(req, companyId);
        if (canReadConfigs) {
            res.json(rows);
            return;
        }
        res.json(rows.map((environment) => redactEnvironmentForRestrictedView(environment)));
    });
    router.get("/companies/:companyId/environments/capabilities", async (req, res) => {
        const companyId = req.params.companyId;
        assertCompanyAccess(req, companyId);
        const pluginDrivers = await listReadyPluginEnvironmentDrivers({
            db,
            workerManager: options.pluginWorkerManager,
        });
        res.json(getEnvironmentCapabilities(AGENT_ADAPTER_TYPES, {
            sandboxProviders: Object.fromEntries(pluginDrivers.map((driver) => [
                driver.driverKey,
                {
                    status: "supported",
                    supportsSavedProbe: true,
                    supportsUnsavedProbe: true,
                    supportsRunExecution: true,
                    supportsReusableLeases: true,
                    displayName: driver.displayName,
                    description: driver.description,
                    source: "plugin",
                    pluginKey: driver.pluginKey,
                    pluginId: driver.pluginId,
                    configSchema: driver.configSchema,
                },
            ])),
        }));
    });
    router.post("/companies/:companyId/environments", validate(createEnvironmentSchema), async (req, res) => {
        const companyId = req.params.companyId;
        await assertCanMutateEnvironments(req, companyId);
        if (req.body.driver === "local") {
            const existingLocal = await svc.list(companyId, { driver: "local" });
            if (existingLocal.length > 0) {
                throw conflict("A local environment already exists for this company.");
            }
        }
        const actor = getActorInfo(req);
        const input = {
            ...req.body,
            config: await normalizeEnvironmentConfigForPersistence({
                db,
                companyId,
                environmentName: req.body.name,
                driver: req.body.driver,
                secretProvider: getConfiguredSecretProvider(),
                config: req.body.config,
                actor: {
                    agentId: actor.agentId,
                    userId: actor.actorType === "user" ? actor.actorId : null,
                },
                pluginWorkerManager: options.pluginWorkerManager,
            }),
        };
        const environment = await svc.create(companyId, input);
        await secrets.syncSecretRefsForTarget(companyId, { targetType: "environment", targetId: environment.id }, await collectEnvironmentSecretRefs({ db, environment }));
        await logActivity(db, {
            companyId,
            actorType: actor.actorType,
            actorId: actor.actorId,
            agentId: actor.agentId,
            runId: actor.runId,
            action: "environment.created",
            entityType: "environment",
            entityId: environment.id,
            details: {
                name: environment.name,
                driver: environment.driver,
                status: environment.status,
            },
        });
        res.status(201).json(environment);
    });
    router.get("/environments/:id", async (req, res) => {
        const environment = await svc.getById(req.params.id);
        if (!environment) {
            res.status(404).json({ error: "Environment not found" });
            return;
        }
        assertCompanyAccess(req, environment.companyId);
        const canReadConfigs = await actorCanReadEnvironmentConfigurations(req, environment.companyId);
        if (canReadConfigs) {
            res.json(environment);
            return;
        }
        res.json(redactEnvironmentForRestrictedView(environment));
    });
    router.get("/environments/:id/leases", async (req, res) => {
        const environment = await svc.getById(req.params.id);
        if (!environment) {
            res.status(404).json({ error: "Environment not found" });
            return;
        }
        assertCompanyAccess(req, environment.companyId);
        const canReadConfigs = await actorCanReadEnvironmentConfigurations(req, environment.companyId);
        if (!canReadConfigs) {
            throw forbidden("Missing permission: environments:manage");
        }
        const leases = await svc.listLeases(environment.id, {
            status: req.query.status,
        });
        res.json(leases);
    });
    router.get("/environment-leases/:leaseId", async (req, res) => {
        const lease = await svc.getLeaseById(req.params.leaseId);
        if (!lease) {
            res.status(404).json({ error: "Environment lease not found" });
            return;
        }
        assertCompanyAccess(req, lease.companyId);
        const canReadConfigs = await actorCanReadEnvironmentConfigurations(req, lease.companyId);
        if (!canReadConfigs) {
            throw forbidden("Missing permission: environments:manage");
        }
        res.json(lease);
    });
    router.patch("/environments/:id", validate(updateEnvironmentSchema), async (req, res) => {
        const existing = await svc.getById(req.params.id);
        if (!existing) {
            res.status(404).json({ error: "Environment not found" });
            return;
        }
        await assertCanMutateEnvironments(req, existing.companyId);
        const actor = getActorInfo(req);
        const nextDriver = req.body.driver ?? existing.driver;
        const nextName = req.body.name ?? existing.name;
        const configSource = req.body.config !== undefined
            ? req.body.driver !== undefined && req.body.driver !== existing.driver
                ? req.body.config
                : {
                    ...parseObject(existing.config),
                    ...parseObject(req.body.config),
                }
            : req.body.driver !== undefined && req.body.driver !== existing.driver
                ? {}
                : existing.config;
        const patch = {
            ...req.body,
            ...(req.body.config !== undefined || req.body.driver !== undefined
                ? {
                    config: await normalizeEnvironmentConfigForPersistence({
                        db,
                        companyId: existing.companyId,
                        environmentName: nextName,
                        driver: nextDriver,
                        secretProvider: getConfiguredSecretProvider(),
                        config: configSource,
                        actor: {
                            agentId: actor.agentId,
                            userId: actor.actorType === "user" ? actor.actorId : null,
                        },
                        pluginWorkerManager: options.pluginWorkerManager,
                    }),
                }
                : {}),
        };
        const environment = await svc.update(existing.id, patch);
        if (!environment) {
            res.status(404).json({ error: "Environment not found" });
            return;
        }
        if (patch.config !== undefined || patch.driver !== undefined) {
            await secrets.syncSecretRefsForTarget(environment.companyId, { targetType: "environment", targetId: environment.id }, await collectEnvironmentSecretRefs({ db, environment }));
        }
        await logActivity(db, {
            companyId: environment.companyId,
            actorType: actor.actorType,
            actorId: actor.actorId,
            agentId: actor.agentId,
            runId: actor.runId,
            action: "environment.updated",
            entityType: "environment",
            entityId: environment.id,
            details: summarizeEnvironmentUpdate(patch, environment),
        });
        res.json(environment);
    });
    router.delete("/environments/:id", async (req, res) => {
        const existing = await svc.getById(req.params.id);
        if (!existing) {
            res.status(404).json({ error: "Environment not found" });
            return;
        }
        await assertCanMutateEnvironments(req, existing.companyId);
        await Promise.all([
            executionWorkspaces.clearEnvironmentSelection(existing.companyId, existing.id),
            issues.clearExecutionWorkspaceEnvironmentSelection(existing.companyId, existing.id),
            projects.clearExecutionWorkspaceEnvironmentSelection(existing.companyId, existing.id),
        ]);
        const removed = await svc.remove(existing.id);
        if (!removed) {
            res.status(404).json({ error: "Environment not found" });
            return;
        }
        const secretId = readSshEnvironmentPrivateKeySecretId(existing);
        if (secretId) {
            await secrets.remove(secretId);
        }
        const actor = getActorInfo(req);
        await logActivity(db, {
            companyId: existing.companyId,
            actorType: actor.actorType,
            actorId: actor.actorId,
            agentId: actor.agentId,
            runId: actor.runId,
            action: "environment.deleted",
            entityType: "environment",
            entityId: removed.id,
            details: {
                name: removed.name,
                driver: removed.driver,
                status: removed.status,
            },
        });
        res.json(removed);
    });
    router.post("/environments/:id/probe", async (req, res) => {
        const environment = await svc.getById(req.params.id);
        if (!environment) {
            res.status(404).json({ error: "Environment not found" });
            return;
        }
        await assertCanMutateEnvironments(req, environment.companyId);
        const actor = getActorInfo(req);
        const probe = await probeEnvironment(db, environment, {
            pluginWorkerManager: options.pluginWorkerManager,
        });
        await logActivity(db, {
            companyId: environment.companyId,
            actorType: actor.actorType,
            actorId: actor.actorId,
            agentId: actor.agentId,
            runId: actor.runId,
            action: "environment.probed",
            entityType: "environment",
            entityId: environment.id,
            details: {
                driver: environment.driver,
                ok: probe.ok,
                summary: probe.summary,
            },
        });
        res.json(probe);
    });
    router.post("/companies/:companyId/environments/probe-config", validate(probeEnvironmentConfigSchema), async (req, res) => {
        const companyId = req.params.companyId;
        await assertCanMutateEnvironments(req, companyId);
        const actor = getActorInfo(req);
        const normalizedConfig = await normalizeEnvironmentConfigForProbe({
            db,
            driver: req.body.driver,
            config: req.body.config,
            pluginWorkerManager: options.pluginWorkerManager,
        });
        const environment = {
            id: "unsaved",
            companyId,
            name: req.body.name?.trim() || "Unsaved environment",
            description: req.body.description ?? null,
            driver: req.body.driver,
            status: "active",
            config: normalizedConfig,
            metadata: req.body.metadata ?? null,
            createdAt: new Date(),
            updatedAt: new Date(),
        };
        const probe = await probeEnvironment(db, environment, {
            pluginWorkerManager: options.pluginWorkerManager,
            resolvedConfig: {
                driver: req.body.driver,
                config: normalizedConfig,
            },
        });
        await logActivity(db, {
            companyId,
            actorType: actor.actorType,
            actorId: actor.actorId,
            agentId: actor.agentId,
            runId: actor.runId,
            action: "environment.probed_unsaved",
            entityType: "environment",
            entityId: "unsaved",
            details: {
                driver: environment.driver,
                ok: probe.ok,
                summary: probe.summary,
                configTopLevelKeyCount: Object.keys(environment.config).length,
            },
        });
        res.json(probe);
    });
    return router;
}
//# sourceMappingURL=environments.js.map