import { Resource, ResourceKind } from "../resource.js";
import { logger } from "../util/logger.js";
import { serializeAccessRule } from "./access-rule.js";
import { isCloudflareApiError } from "./api-error.js";
import { extractCloudflareResult, } from "./api-response.js";
import { createCloudflareApi, } from "./api.js";
/**
 * Type guard for {@link AccessPolicy}.
 */
export function isAccessPolicy(resource) {
    return resource?.[ResourceKind] === "cloudflare::AccessPolicy";
}
/**
 * Creates a reusable Cloudflare Zero Trust [Access policy](https://developers.cloudflare.com/cloudflare-one/policies/access/)
 * that can be attached to one or more {@link AccessApplication} resources.
 *
 * @example
 * // Allow employees from a specific email domain.
 * const employees = await AccessPolicy("employees", {
 *   name: "Employees",
 *   decision: "allow",
 *   include: [{ email_domain: { domain: "acme.com" } }],
 * });
 *
 * @example
 * // Bypass Access for an office IP range.
 * const officeBypass = await AccessPolicy("office-bypass", {
 *   name: "Office Bypass",
 *   decision: "bypass",
 *   include: [{ ip: { ip: "203.0.113.0/24" } }],
 * });
 *
 * @example
 * // Reference an AccessGroup and require approval.
 * const sensitive = await AccessPolicy("sensitive", {
 *   name: "Sensitive admin access",
 *   decision: "allow",
 *   include: [{ group: { id: adminGroup } }],
 *   approvalRequired: true,
 *   approvalGroups: [
 *     { approvalsNeeded: 2, emailAddresses: ["security@acme.com"] },
 *   ],
 *   isolationRequired: true,
 * });
 */
export const AccessPolicy = Resource("cloudflare::AccessPolicy", async function (id, props) {
    const api = await createCloudflareApi(props);
    const name = props.name ?? this.scope.createPhysicalName(id);
    const basePath = `/accounts/${api.accountId}/access/policies`;
    if (this.phase === "delete") {
        if (this.output?.id && props.delete !== false) {
            await deleteAccessPolicy(api, this.output.id);
        }
        return this.destroy();
    }
    if (!props.include || props.include.length === 0) {
        throw new Error(`AccessPolicy "${name}" requires at least one rule in 'include'.`);
    }
    // decision is immutable — recreate if it changed.
    if (this.phase === "update" &&
        this.output &&
        this.output.decision !== props.decision) {
        this.replace(true);
    }
    const body = {
        name,
        decision: props.decision,
        reusable: true,
        include: props.include.map(serializeAccessRule),
        exclude: (props.exclude ?? []).map(serializeAccessRule),
        require: (props.require ?? []).map(serializeAccessRule),
    };
    if (props.approvalRequired !== undefined)
        body.approval_required = props.approvalRequired;
    if (props.approvalGroups)
        body.approval_groups = props.approvalGroups.map((g) => ({
            approvals_needed: g.approvalsNeeded,
            email_addresses: g.emailAddresses,
            email_list_uuid: g.emailListUuid,
        }));
    if (props.purposeJustificationRequired !== undefined)
        body.purpose_justification_required = props.purposeJustificationRequired;
    if (props.purposeJustificationPrompt !== undefined)
        body.purpose_justification_prompt = props.purposeJustificationPrompt;
    if (props.isolationRequired !== undefined)
        body.isolation_required = props.isolationRequired;
    if (props.sessionDuration !== undefined)
        body.session_duration = props.sessionDuration;
    if (props.mfaConfig)
        body.mfa_config = {
            allowed_authenticators: props.mfaConfig.allowedAuthenticators,
            mfa_disabled: props.mfaConfig.mfaDisabled,
            session_duration: props.mfaConfig.sessionDuration,
        };
    if (props.connectionRules?.rdp)
        body.connection_rules = {
            rdp: {
                allowed_clipboard_formats: props.connectionRules.rdp.allowedClipboardFormats,
            },
        };
    let policy;
    if (this.phase === "update" && this.output?.id) {
        policy = await extractCloudflareResult(`update access policy "${name}"`, api.put(`${basePath}/${this.output.id}`, body));
    }
    else {
        const adopt = props.adopt ?? this.scope.adopt;
        try {
            policy = await extractCloudflareResult(`create access policy "${name}"`, api.post(basePath, body));
        }
        catch (err) {
            if (adopt && isAccessDuplicateNameError(err)) {
                const existing = await findAccessPolicyByName(api, name);
                if (!existing) {
                    throw new Error(`Access policy "${name}" already exists but could not be found for adoption.`, { cause: err });
                }
                logger.log(`Adopting existing access policy "${name}" (${existing.id})`);
                policy = await extractCloudflareResult(`adopt access policy "${name}"`, api.put(`${basePath}/${existing.id}`, body));
            }
            else {
                throw err;
            }
        }
    }
    return {
        id: policy.id,
        name: policy.name,
        decision: policy.decision,
        include: props.include,
        exclude: props.exclude,
        require: props.require,
        approvalRequired: props.approvalRequired,
        approvalGroups: props.approvalGroups,
        purposeJustificationRequired: props.purposeJustificationRequired,
        purposeJustificationPrompt: props.purposeJustificationPrompt,
        isolationRequired: props.isolationRequired,
        mfaConfig: props.mfaConfig,
        sessionDuration: props.sessionDuration,
        connectionRules: props.connectionRules,
        appCount: policy.app_count ?? 0,
        createdAt: policy.created_at,
        updatedAt: policy.updated_at,
    };
});
function isAccessDuplicateNameError(err) {
    if (isCloudflareApiError(err, { status: 409 }) ||
        isCloudflareApiError(err, { status: 400 })) {
        const data = err.errorData;
        return (Array.isArray(data) &&
            data.some((e) => "message" in e && /already exists/i.test(String(e.message))));
    }
    return false;
}
async function findAccessPolicyByName(api, name) {
    let page = 1;
    const perPage = 50;
    while (true) {
        const response = await api.get(`/accounts/${api.accountId}/access/policies?page=${page}&per_page=${perPage}`);
        if (!response.ok)
            return null;
        const data = (await response.json());
        const match = data.result.find((p) => p.name === name);
        if (match)
            return match;
        const info = data.result_info;
        if (!info || info.page * info.per_page >= info.total_count)
            return null;
        page++;
    }
}
async function deleteAccessPolicy(api, policyId) {
    const response = await api.delete(`/accounts/${api.accountId}/access/policies/${policyId}`);
    if (!response.ok && response.status !== 404) {
        logger.error(`Error deleting access policy ${policyId}: ${response.status} ${response.statusText}`);
    }
}
//# sourceMappingURL=access-policy.js.map