import { alchemy } from "../alchemy.js";
import { Resource } from "../resource.js";
import { Scope } from "../scope.js";
import { Secret } from "../secret.js";
import { logger } from "../util/logger.js";
import { CloudflareApiError, handleApiError } from "./api-error.js";
import { extractCloudflareResult, } from "./api-response.js";
import { createCloudflareApi, } from "./api.js";
/**
 * Represents a Cloudflare Hyperdrive configuration.
 *
 * @example
 * // Create a basic Hyperdrive connection to a PostgreSQL database
 * const basicHyperdrive = await Hyperdrive("my-postgres-db", {
 *   name: "my-postgres-db",
 *   origin: {
 *     database: "postgres",
 *     host: "database.example.com",
 *     password: alchemy.secret("your-password"),
 *     port: 5432,
 *     user: "postgres"
 *   }
 * });
 *
 * @example
 * // Create a basic Hyperdrive connection to a MySQL database
 * const mysqlHyperdrive = await Hyperdrive("my-mysql-db", {
 *   name: "my-mysql-db",
 *   origin: {
 *     database: "mydb",
 *     host: "mysql.example.com",
 *     password: alchemy.secret("your-password"),
 *     port: 3306,
 *     scheme: "mysql",
 *     user: "mysql_user"
 *   }
 * });
 *
 * @example
 * // Create a Hyperdrive with caching disabled
 * const noCacheHyperdrive = await Hyperdrive("no-cache-db", {
 *   name: "no-cache-db",
 *   origin: {
 *     database: "postgres",
 *     host: "database.example.com",
 *     password: alchemy.secret(process.env.DB_PASSWORD),
 *     port: 5432,
 *     user: "postgres"
 *   },
 *   caching: {
 *     disabled: true
 *   }
 * });
 *
 * @example
 * // Create a Hyperdrive with mTLS configuration
 * const mtlsHyperdrive = await Hyperdrive("secure-db", {
 *   name: "secure-db",
 *   origin: {
 *     database: "postgres",
 *     host: "database.example.com",
 *     password: alchemy.secret(process.env.DB_PASSWORD),
 *     port: 5432,
 *     user: "postgres"
 *   },
 *   mtls: {
 *     ca_certificate_id: "00000000-0000-0000-0000-0000000000",
 *     mtls_certificate_id: "00000000-0000-0000-0000-0000000000",
 *     sslmode: "verify-full"
 *   }
 * });
 *
 * @example
 * // Create a Hyperdrive with access client credentials
 * const accessHyperdrive = await Hyperdrive("access-db", {
 *   name: "access-db",
 *   origin: {
 *     database: "postgres",
 *     host: "database.example.com",
 *     access_client_id: "client-id",
 *     access_client_secret: alchemy.secret(process.env.ACCESS_CLIENT_SECRET),
 *     port: 5432,
 *     user: "postgres"
 *   }
 * });
 *
 * @example
 * // Create a Hyperdrive that runs remotely even in local dev mode
 * const remoteDevHyperdrive = await Hyperdrive("remote-dev-db", {
 *   name: "remote-dev-db",
 *   origin: {
 *     database: "postgres",
 *     host: "database.example.com",
 *     password: alchemy.secret(process.env.DB_PASSWORD),
 *     port: 5432,
 *     user: "postgres"
 *   },
 *   dev: {
 *     remote: true
 *   }
 * });
 */
export async function Hyperdrive(id, props) {
    const origin = normalizeHyperdriveOrigin(props.origin);
    const dev = {
        origin: toConnectionString(normalizeHyperdriveOrigin(props.dev?.origin ?? origin)),
        remote: props.dev?.remote ?? false,
        force: Scope.current.local,
    };
    return await _Hyperdrive(id, {
        ...props,
        origin,
        dev,
    });
}
const _Hyperdrive = Resource("cloudflare::Hyperdrive", async function (id, props) {
    const hyperdriveId = props.hyperdriveId || this.output?.hyperdriveId;
    const adopt = props.adopt || this.scope.adopt;
    const name = props.name ?? this.output?.name ?? this.scope.createPhysicalName(id);
    if (this.scope.local && !props.dev?.remote) {
        return {
            id,
            hyperdriveId: hyperdriveId || "",
            name,
            origin: props.origin,
            caching: props.caching,
            mtls: props.mtls,
            dev: {
                origin: props.dev.origin,
                remote: false,
            },
            type: "hyperdrive",
        };
    }
    const api = await createCloudflareApi(props);
    if (this.phase === "delete") {
        if (props.delete === false) {
            logger.warn(`Delete is disabled for hyperdrive "${name}" (${id}), removing from state`);
            return this.destroy();
        }
        if (!hyperdriveId) {
            logger.warn(`No hyperdriveId found for ${id}, skipping delete`);
            return this.destroy();
        }
        try {
            const deleteResponse = await api.delete(`/accounts/${api.accountId}/hyperdrive/configs/${hyperdriveId}`);
            // Only swallow 404 Not Found errors, all other errors should be handled
            if (!deleteResponse.ok && deleteResponse.status !== 404) {
                await handleApiError(deleteResponse, "delete", "hyperdrive", id);
            }
        }
        catch (error) {
            logger.error(`Error deleting Hyperdrive ${id}:`, error);
            throw error;
        }
        return this.destroy();
    }
    // Prepare request body with unwrapped secrets
    const requestBody = prepareRequestBody({ ...props, name });
    let result;
    if (hyperdriveId) {
        result = await extractCloudflareResult(`update hyperdrive config "${hyperdriveId}"`, api.put(`/accounts/${api.accountId}/hyperdrive/configs/${hyperdriveId}`, requestBody));
    }
    else {
        try {
            result = await extractCloudflareResult(`create hyperdrive config "${name}"`, api.post(`/accounts/${api.accountId}/hyperdrive/configs`, requestBody));
        }
        catch (error) {
            if (error instanceof CloudflareApiError &&
                error.errorData.some((e) => e.code === 2017)) {
                if (!adopt) {
                    throw new Error(`Hyperdrive config "${name}" already exists. Use adopt: true to adopt it.`, { cause: error });
                }
                const existing = await findHyperdriveConfigByName(api, name);
                if (!existing) {
                    throw new Error(`Hyperdrive config "${name}" failed to create due to name conflict and could not be found for adoption.`, { cause: error });
                }
                result = await extractCloudflareResult(`adopt hyperdrive config "${name}"`, api.put(`/accounts/${api.accountId}/hyperdrive/configs/${existing.id}`, requestBody));
            }
            else {
                throw error;
            }
        }
    }
    // Construct the output object from API response and props
    return {
        id,
        hyperdriveId: result.id, // Store the Cloudflare-assigned UUID
        name: result.name,
        origin: props.origin,
        caching: result.caching,
        mtls: result.mtls,
        dev: {
            origin: props.dev.origin,
            remote: props.dev.remote ?? false,
        },
        type: "hyperdrive",
    };
});
async function findHyperdriveConfigByName(api, name, page = 1) {
    const response = await api.get(`/accounts/${api.accountId}/hyperdrive/configs?page=${page}`);
    const data = await response.json();
    const found = data.result.find((config) => config.name === name);
    if (found) {
        return found;
    }
    if (data.result_info?.total_pages && page < data.result_info.total_pages) {
        return await findHyperdriveConfigByName(api, name, page + 1);
    }
    return null;
}
/**
 * Prepare the request body by unwrapping secret values
 */
function prepareRequestBody(props) {
    const requestBody = { ...props };
    // Remove internal props
    delete requestBody.hyperdriveId;
    // Deep clone and unwrap secrets in the origin object
    if ("password" in props.origin) {
        // Regular origin with password
        requestBody.origin = {
            ...props.origin,
            password: Secret.unwrap(props.origin.password),
            scheme: props.origin.scheme ?? "postgres",
        };
    }
    else if ("access_client_secret" in props.origin) {
        // Origin with access client secret
        requestBody.origin = {
            ...props.origin,
            access_client_secret: Secret.unwrap(props.origin.access_client_secret),
        };
    }
    return requestBody;
}
/**
 * Converts a HyperdriveOriginInput to a HyperdriveOrigin.
 * This includes:
 * - parsing the origin from a string
 * - ensuring the scheme is "postgres" or "mysql"
 * - normalizing the port to a number with default values
 * - wrapping secrets in a Secret object
 * @internal - Exported for testing
 */
export const normalizeHyperdriveOrigin = (input) => {
    const origin = Secret.unwrap(input);
    if (typeof origin === "string") {
        const url = new URL(origin);
        const scheme = normalizeScheme(url.protocol.slice(0, -1));
        return {
            scheme,
            user: url.username,
            password: alchemy.secret(url.password),
            host: url.hostname,
            port: normalizePort(scheme, url.port),
            database: url.pathname.slice(1),
        };
    }
    const scheme = normalizeScheme(origin.scheme);
    return {
        ...origin,
        ...("password" in origin && {
            password: Secret.wrap(origin.password),
        }),
        ...("access_client_secret" in origin && {
            access_client_secret: Secret.wrap(origin.access_client_secret),
        }),
        scheme,
        port: normalizePort(scheme, origin.port),
    };
};
const normalizeScheme = (scheme) => {
    if (!scheme || scheme === "postgres" || scheme === "postgresql") {
        return "postgres";
    }
    if (scheme === "mysql" || scheme === "mysql2") {
        return "mysql";
    }
    throw new Error(`Unsupported database connection scheme "${scheme}" for Hyperdrive (expected "postgres" or "mysql")`);
};
const normalizePort = (scheme, port) => {
    if (typeof port === "number") {
        return port;
    }
    if (port) {
        return Number.parseInt(port, 10);
    }
    return scheme === "postgres" ? 5432 : 3306;
};
const toConnectionString = (origin) => {
    const password = Secret.unwrap("password" in origin ? origin.password : origin.access_client_secret);
    return new Secret(`${origin.scheme}://${origin.user}:${password}@${origin.host}:${origin.port}/${origin.database}`);
};
//# sourceMappingURL=hyperdrive.js.map