import { alchemy } from "../alchemy.js";
import { Resource, ResourceKind } from "../resource.js";
import { logger } from "../util/logger.js";
import { isCloudflareApiError } from "./api-error.js";
import { extractCloudflareResult, } from "./api-response.js";
import { createCloudflareApi, } from "./api.js";
/**
 * Type guard for {@link AccessServiceToken}.
 */
export function isAccessServiceToken(resource) {
    return resource?.[ResourceKind] === "cloudflare::AccessServiceToken";
}
/**
 * Creates a Cloudflare Zero Trust [Access service token](https://developers.cloudflare.com/cloudflare-one/identity/service-tokens/)
 * for machine-to-machine authentication against Access-protected applications.
 *
 * The returned `clientSecret` is only available on creation — store it
 * immediately. Adopted tokens have `clientSecret: undefined`.
 *
 * @example
 * // Basic service token (defaults to 1 year duration)
 * const token = await AccessServiceToken("ci-token", {
 *   name: "ci-runner",
 * });
 *
 * @example
 * // Custom duration
 * const shortLived = await AccessServiceToken("preview", {
 *   name: "preview-deploy-token",
 *   duration: "720h", // 30 days
 * });
 */
export const AccessServiceToken = Resource("cloudflare::AccessServiceToken", async function (id, props) {
    const api = await createCloudflareApi(props);
    const name = props.name ?? this.scope.createPhysicalName(id);
    const basePath = `/accounts/${api.accountId}/access/service_tokens`;
    if (this.phase === "delete") {
        if (this.output?.id && props.delete !== false) {
            await deleteAccessServiceToken(api, this.output.id);
        }
        return this.destroy();
    }
    const body = { name };
    if (props.duration)
        body.duration = props.duration;
    let token;
    if (this.phase === "update" && this.output?.id) {
        token = await extractCloudflareResult(`update access service token "${name}"`, api.put(`${basePath}/${this.output.id}`, body));
    }
    else {
        const adopt = props.adopt ?? this.scope.adopt;
        try {
            token = await extractCloudflareResult(`create access service token "${name}"`, api.post(basePath, body));
        }
        catch (err) {
            if (adopt && isAccessDuplicateNameError(err)) {
                const existing = await findAccessServiceTokenByName(api, name);
                if (!existing) {
                    throw new Error(`Service token "${name}" already exists but could not be found for adoption.`, { cause: err });
                }
                logger.log(`Adopting existing access service token "${name}" (${existing.id})`);
                token = await extractCloudflareResult(`adopt access service token "${name}"`, api.put(`${basePath}/${existing.id}`, body));
            }
            else {
                throw err;
            }
        }
    }
    // clientSecret is only returned on creation; retain on subsequent updates.
    const clientSecret = token.client_secret
        ? alchemy.secret(token.client_secret)
        : this.output?.clientSecret;
    return {
        id: token.id,
        name: token.name,
        clientId: token.client_id,
        clientSecret,
        duration: token.duration,
        createdAt: token.created_at,
        updatedAt: token.updated_at,
    };
});
/**
 * Cloudflare returns error code 12132 ("Access service token already exists")
 * for duplicate names.
 */
function isAccessDuplicateNameError(err) {
    if (isCloudflareApiError(err, { status: 409 }) ||
        isCloudflareApiError(err, { status: 400 })) {
        const data = err.errorData;
        if (Array.isArray(data) &&
            data.some((e) => "message" in e && /already exists/i.test(String(e.message)))) {
            return true;
        }
    }
    return false;
}
/**
 * Look up an existing service token by name across paginated results.
 */
async function findAccessServiceTokenByName(api, name) {
    let page = 1;
    const perPage = 50;
    while (true) {
        const response = await api.get(`/accounts/${api.accountId}/access/service_tokens?page=${page}&per_page=${perPage}`);
        if (!response.ok)
            return null;
        const data = (await response.json());
        const match = data.result.find((t) => t.name === name);
        if (match)
            return match;
        const info = data.result_info;
        if (!info || info.page * info.per_page >= info.total_count)
            return null;
        page++;
    }
}
/**
 * Delete a service token. No-op on 404.
 */
async function deleteAccessServiceToken(api, tokenId) {
    const response = await api.delete(`/accounts/${api.accountId}/access/service_tokens/${tokenId}`);
    if (!response.ok && response.status !== 404) {
        logger.error(`Error deleting access service token ${tokenId}: ${response.status} ${response.statusText}`);
    }
}
//# sourceMappingURL=access-service-token.js.map