import { Resource } from "../resource.js";
import { logger } from "../util/logger.js";
import { createGitHubClient, verifyGitHubAuth } from "./client.js";
/**
 * Resource for managing GitHub repository environments
 *
 * Note: If preventSelfReview is true, at least one reviewer must be specified.
 *
 * Branch policies are efficiently managed with proper diffing:
 * - Only manages branch patterns configured through this resource
 * - Preserves any manually added branch patterns outside this resource
 * - When updating, only modifies patterns that have changed from previous state
 * - Compares against previous resource state, not current environment state
 * - Safely handles policy type changes
 *
 * @example
 * // Create a basic environment with no protection rules
 * const devEnv = await RepositoryEnvironment("dev-environment", {
 *   owner: "my-org",
 *   repository: "my-repo",
 *   name: "development"
 * });
 *
 * @example
 * // Create a production environment with approval requirements
 * const prodEnv = await RepositoryEnvironment("prod-environment", {
 *   owner: "my-org",
 *   repository: "my-repo",
 *   name: "production",
 *   waitTimer: 10, // 10 minute delay
 *   preventSelfReview: true,
 *   reviewers: {
 *     teams: ["platform-team"], // team name
 *     users: ["security-admin"] // username
 *   },
 *   deploymentBranchPolicy: {
 *     protectedBranches: true,
 *     customBranchPolicies: false
 *   }
 * });
 *
 * @example
 * // Create an environment with reviewer IDs
 * const stagingEnv = await RepositoryEnvironment("staging-environment", {
 *   owner: "my-org",
 *   repository: "my-repo",
 *   name: "staging",
 *   reviewers: {
 *     teams: [1234567], // team ID
 *     users: [7654321]  // user ID
 *   },
 *   deploymentBranchPolicy: {
 *     protectedBranches: false,
 *     customBranchPolicies: true
 *   },
 *   branchPatterns: ["main", "release/*"]
 * });
 */
export const RepositoryEnvironment = Resource("github::RepositoryEnvironment", async function (id, props) {
    // Create authenticated Octokit client
    const octokit = await createGitHubClient({
        token: props.token,
    });
    // Verify authentication and permissions
    if (!this.quiet) {
        await verifyGitHubAuth(octokit, props.owner, props.repository);
    }
    const environmentName = props.name ?? this.output?.name ?? this.scope.createPhysicalName(id);
    if (this.phase === "update" && this.output.name !== environmentName) {
        this.replace();
    }
    if (this.phase === "delete") {
        if (this.output?.id) {
            try {
                // Delete the environment
                await octokit.rest.repos.deleteAnEnvironment({
                    owner: props.owner,
                    repo: props.repository,
                    environment_name: environmentName,
                });
            }
            catch (error) {
                // Ignore 404 errors (environment already deleted)
                if (error.status === 404) {
                    logger.log("Environment doesn't exist, ignoring");
                }
                else {
                    throw error;
                }
            }
        }
        // Return void (a deleted resource has no content)
        return this.destroy();
    }
    try {
        // Check if the environment already exists
        let environmentId; // Use undefined instead of 0
        try {
            const { data: environments } = await octokit.rest.repos.getAllEnvironments({
                owner: props.owner,
                repo: props.repository,
            });
            const existingEnv = environments.environments?.find((env) => env.name.toLowerCase() === environmentName.toLowerCase());
            if (existingEnv?.id) {
                environmentId = existingEnv.id;
            }
        }
        catch (error) {
            // If it's a 404, the environment doesn't exist, which is fine
            if (error.status !== 404) {
                throw error;
            }
        }
        // Convert reviewers to API format
        const reviewers = [];
        // Process user reviewers
        if (props.reviewers?.users && props.reviewers.users.length > 0) {
            for (const user of props.reviewers.users) {
                if (typeof user === "number") {
                    // If a numeric ID is provided, use it directly
                    reviewers.push({
                        type: "User",
                        id: user,
                    });
                }
                else {
                    // If a username string is provided, look up the ID
                    const { data: userData } = await octokit.rest.users.getByUsername({
                        username: user,
                    });
                    reviewers.push({
                        type: "User",
                        id: userData.id,
                    });
                }
            }
        }
        // Process team reviewers
        if (props.reviewers?.teams && props.reviewers.teams.length > 0) {
            for (const team of props.reviewers.teams) {
                if (typeof team === "number") {
                    // If a numeric ID is provided, use it directly
                    reviewers.push({
                        type: "Team",
                        id: team,
                    });
                }
                else {
                    // If a team name string is provided, look up the ID
                    // Check if team name is in the format 'org/team-name'
                    const teamParts = team.includes("/")
                        ? team.split("/")
                        : [props.owner, team];
                    if (teamParts.length !== 2) {
                        throw new Error(`Invalid team format: ${team}. Expected format: "org/team-slug" or just "team-slug"`);
                    }
                    const [org, teamSlug] = teamParts;
                    const { data: teamData } = await octokit.rest.teams.getByName({
                        org,
                        team_slug: teamSlug,
                    });
                    reviewers.push({
                        type: "Team",
                        id: teamData.id,
                    });
                }
            }
        }
        // Create or update the environment
        if (environmentId === undefined) {
            // Create the environment
            const { data: createdEnv } = await octokit.rest.repos.createOrUpdateEnvironment({
                owner: props.owner,
                repo: props.repository,
                environment_name: environmentName,
                wait_timer: props.waitTimer,
                prevent_self_review: props.preventSelfReview,
                reviewers: reviewers.length > 0 ? reviewers : undefined,
                deployment_branch_policy: props.deploymentBranchPolicy
                    ? {
                        protected_branches: props.deploymentBranchPolicy.protectedBranches ?? false,
                        custom_branch_policies: props.deploymentBranchPolicy.customBranchPolicies ?? false,
                    }
                    : undefined,
            });
            environmentId = createdEnv.id;
            // If there are specific branch patterns, set them
            if (props.deploymentBranchPolicy?.customBranchPolicies === true &&
                props.branchPatterns &&
                props.branchPatterns.length > 0) {
                // Add branch patterns one by one
                for (const pattern of props.branchPatterns) {
                    await octokit.rest.repos.createDeploymentBranchPolicy({
                        owner: props.owner,
                        repo: props.repository,
                        environment_name: environmentName,
                        name: pattern,
                    });
                }
            }
        }
        else {
            // Update the environment
            await octokit.rest.repos.createOrUpdateEnvironment({
                owner: props.owner,
                repo: props.repository,
                environment_name: environmentName,
                wait_timer: props.waitTimer,
                prevent_self_review: props.preventSelfReview,
                reviewers: reviewers.length > 0 ? reviewers : undefined,
                deployment_branch_policy: props.deploymentBranchPolicy
                    ? {
                        protected_branches: props.deploymentBranchPolicy.protectedBranches ?? false,
                        custom_branch_policies: props.deploymentBranchPolicy.customBranchPolicies ?? false,
                    }
                    : undefined,
            });
            // If there are specific branch patterns, update them
            if (props.deploymentBranchPolicy?.customBranchPolicies === true) {
                // Get existing branch policies to understand what's currently configured
                const { data: existingPolicies } = await octokit.rest.repos.listDeploymentBranchPolicies({
                    owner: props.owner,
                    repo: props.repository,
                    environment_name: environmentName,
                });
                const existingPatterns = existingPolicies.branch_policies.map((policy) => policy.name);
                const newPatterns = props.branchPatterns || [];
                // For updates, keep track of which branch policies we should manage
                // This set identifies patterns that were previously managed by this resource
                // If we don't have previous props, use an empty set
                const previouslyManagedPatterns = new Set(this.phase === "update" &&
                    this.props?.deploymentBranchPolicy?.customBranchPolicies === true
                    ? this.props.branchPatterns || []
                    : []);
                // Patterns to add (in new config but not in existing)
                const patternsToAdd = newPatterns.filter((pattern) => !existingPatterns.includes(pattern));
                // Patterns to delete (were managed by us previously but not in new config)
                const patternsToDelete = existingPatterns.filter((pattern) => previouslyManagedPatterns.has(pattern) &&
                    !newPatterns.includes(pattern));
                // Delete policies that are no longer needed
                for (const patternToDelete of patternsToDelete) {
                    const policy = existingPolicies.branch_policies.find((p) => p.name === patternToDelete);
                    if (policy?.id) {
                        await octokit.rest.repos.deleteDeploymentBranchPolicy({
                            owner: props.owner,
                            repo: props.repository,
                            environment_name: environmentName,
                            branch_policy_id: policy.id,
                        });
                    }
                }
                // Add new branch patterns
                for (const pattern of patternsToAdd) {
                    await octokit.rest.repos.createDeploymentBranchPolicy({
                        owner: props.owner,
                        repo: props.repository,
                        environment_name: environmentName,
                        name: pattern,
                    });
                }
            }
        }
        // Get the updated environment details
        const { data: env } = await octokit.rest.repos.getEnvironment({
            owner: props.owner,
            repo: props.repository,
            environment_name: environmentName,
        });
        // Return environment details
        return {
            id: `${props.owner}/${props.repository}/${environmentName}`,
            environmentId: environmentId || env.id,
            owner: props.owner,
            repository: props.repository,
            name: environmentName,
            waitTimer: props.waitTimer,
            preventSelfReview: props.preventSelfReview,
            adminBypass: props.adminBypass,
            reviewers: props.reviewers,
            deploymentBranchPolicy: props.deploymentBranchPolicy,
            branchPatterns: props.branchPatterns,
            token: props.token,
            updatedAt: new Date().toISOString(),
        };
    }
    catch (error) {
        if (error.status === 403 &&
            error.message?.includes("Must have admin rights")) {
            logger.error("\n⚠️ Error creating/updating GitHub environment: You must have admin rights to the repository.");
            logger.error("Make sure your GitHub token has the required permissions (repo scope for private repos).\n");
        }
        else {
            logger.error("Error creating/updating GitHub environment:", error.message);
        }
        throw error;
    }
});
//# sourceMappingURL=repository-environment.js.map