import { CreateOpenIDConnectProviderCommand, DeleteOpenIDConnectProviderCommand, GetOpenIDConnectProviderCommand, GetRoleCommand, IAMClient, UpdateAssumeRolePolicyCommand, } from "@aws-sdk/client-iam";
import { Resource } from "../../resource.js";
import { logger } from "../../util/logger.js";
import { AccountId } from "../account-id.js";
/**
 * AWS OIDC Provider Resource for GitHub Actions
 *
 * Creates and manages an OpenID Connect (OIDC) identity provider in AWS IAM
 * for GitHub Actions workflows. This enables secure, token-based authentication
 * between GitHub Actions and AWS without storing long-term credentials.
 *
 * @example
 * // Create an OIDC provider for all branches
 * const provider = await OIDCProvider("github", {
 *   owner: "my-org",
 *   repository: "my-repo",
 *   roleArn: "arn:aws:iam::123456789012:role/github-actions",
 *   thumbprint: "6938fd4d98bab03faadb97b34396831e3780aea1"
 * });
 *
 * @example
 * // Create an OIDC provider restricted to specific branches and environments
 * const provider = await OIDCProvider("github-restricted", {
 *   owner: "my-org",
 *   repository: "my-repo",
 *   branches: ["main", "prod"],
 *   environments: ["staging", "production"],
 *   roleArn: "arn:aws:iam::123456789012:role/github-actions",
 *   thumbprint: "6938fd4d98bab03faadb97b34396831e3780aea1",
 *   maxSessionDuration: 7200
 * });
 */
const TRUST_POLICY_SID = "GitHubOIDCTrust";
export const OIDCProvider = Resource("aws::OIDCProvider", async function (_id, props) {
    // Initialize AWS SDK client
    const client = new IAMClient({
        region: props.region,
    });
    if (this.phase === "delete") {
        if (this.output?.providerArn) {
            try {
                // First, remove our trust policy statement from the role
                const getRole = await client.send(new GetRoleCommand({
                    RoleName: props.roleArn.split("/").pop(),
                }));
                if (getRole.Role?.AssumeRolePolicyDocument) {
                    const policy = JSON.parse(decodeURIComponent(getRole.Role.AssumeRolePolicyDocument));
                    // Remove our specific statement while preserving others
                    policy.Statement = policy.Statement.filter((stmt) => stmt.Sid !== TRUST_POLICY_SID);
                    await client.send(new UpdateAssumeRolePolicyCommand({
                        RoleName: props.roleArn.split("/").pop(),
                        PolicyDocument: JSON.stringify(policy),
                    }));
                }
                // Then delete the OIDC provider if we're the last user
                const provider = await client.send(new GetOpenIDConnectProviderCommand({
                    OpenIDConnectProviderArn: this.output.providerArn,
                }));
                // Only delete the provider if it exists and has no other tags
                if (provider && (!provider.Tags || provider.Tags.length === 0)) {
                    await client.send(new DeleteOpenIDConnectProviderCommand({
                        OpenIDConnectProviderArn: this.output.providerArn,
                    }));
                }
            }
            catch (error) {
                // Log but don't throw on cleanup errors
                logger.error("Error during cleanup:", error);
            }
        }
        return this.destroy();
    }
    try {
        const url = "https://token.actions.githubusercontent.com";
        const thumbprint = props.thumbprint;
        // Create or get existing OIDC provider
        let providerArn;
        try {
            const createProvider = await client.send(new CreateOpenIDConnectProviderCommand({
                Url: url,
                ClientIDList: ["sts.amazonaws.com"],
                ThumbprintList: [thumbprint],
                Tags: [
                    {
                        Key: "ManagedBy",
                        Value: "alchemy",
                    },
                ],
            }));
            providerArn = createProvider.OpenIDConnectProviderArn;
        }
        catch (error) {
            if (error.name === "EntityAlreadyExistsException") {
                // Provider exists, use its ARN
                providerArn = `arn:aws:iam::${await AccountId()}:oidc-provider/${url}`;
            }
            else {
                throw error;
            }
        }
        // Get current role policy
        const getRole = await client.send(new GetRoleCommand({
            RoleName: props.roleArn.split("/").pop(),
        }));
        let policy;
        if (getRole.Role?.AssumeRolePolicyDocument) {
            policy = JSON.parse(decodeURIComponent(getRole.Role.AssumeRolePolicyDocument));
        }
        else {
            policy = {
                Version: "2012-10-17",
                Statement: [],
            };
        }
        // Construct the trust policy conditions
        const conditions = {
            StringEquals: {
                "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
                "token.actions.githubusercontent.com:sub": `repo:${props.owner}/${props.repository}:`,
            },
        };
        if (props.branches?.length) {
            conditions.StringEquals["token.actions.githubusercontent.com:sub"] =
                props.branches.map((branch) => `repo:${props.owner}/${props.repository}:ref:refs/heads/${branch}`);
        }
        if (props.environments?.length) {
            conditions.StringEquals["token.actions.githubusercontent.com:sub"] =
                props.environments.map((env) => `repo:${props.owner}/${props.repository}:environment:${env}`);
        }
        // Create our policy statement
        const ourStatement = {
            Sid: TRUST_POLICY_SID,
            Effect: "Allow",
            Principal: {
                Federated: providerArn,
            },
            Action: "sts:AssumeRoleWithWebIdentity",
            Condition: conditions,
        };
        // Remove any existing statement with our SID and add the new one
        policy.Statement = [
            ...policy.Statement.filter((stmt) => stmt.Sid !== TRUST_POLICY_SID),
            ourStatement,
        ];
        // Update the role's trust policy
        await client.send(new UpdateAssumeRolePolicyCommand({
            RoleName: props.roleArn.split("/").pop(),
            PolicyDocument: JSON.stringify(policy),
        }));
        return {
            ...props,
            providerArn,
            createdAt: Date.now(),
        };
    }
    catch (error) {
        logger.error("Error configuring OIDC provider:", error);
        throw error;
    }
});
//# sourceMappingURL=oidc-provider.js.map