import { XMLParser } from "fast-xml-parser";
import { Resource } from "../../resource.js";
import { ignore } from "../../util/ignore.js";
import { logger } from "../../util/logger.js";
import { mergeTimeoutConfig, waitForResourceState, } from "../../util/timeout.js";
import { retry } from "../retry.js";
import { callEC2Api, createEC2Client } from "./utils.js";
/**
 * AWS Route Resource
 *
 * Creates and manages individual routes within route tables, directing traffic
 * to various targets like Internet Gateways, NAT Gateways, or instances.
 *
 * Supports AWS credential overrides at the resource level, allowing you to deploy Routes
 * to different AWS accounts or regions than the default scope configuration.
 *
 * @example
 * // Create a default route to Internet Gateway
 * const publicRoute = await Route("public-default-route", {
 *   routeTable: publicRouteTable,
 *   destinationCidrBlock: "0.0.0.0/0",
 *   target: { internetGateway: mainIgw }
 * });
 *
 * @example
 * // Create a default route to NAT Gateway for private subnets
 * const privateRoute = await Route("private-default-route", {
 *   routeTable: privateRouteTable,
 *   destinationCidrBlock: "0.0.0.0/0",
 *   target: { natGateway: mainNat }
 * });
 *
 * @example
 * // Create Route with AWS credential overrides
 * const crossAccountRoute = await Route("cross-account-route", {
 *   routeTable: mainRouteTable,
 *   destinationCidrBlock: "0.0.0.0/0",
 *   target: { internetGateway: mainIgw },
 *   // Override AWS credentials for this specific resource
 *   region: "us-east-1",
 *   profile: "production-account",
 * });
 *
 * @example
 * // Create Route in different region with role assumption
 * const multiRegionRoute = await Route("multi-region-route", {
 *   routeTable: euRouteTable,
 *   destinationCidrBlock: "0.0.0.0/0",
 *   target: { internetGateway: euIgw },
 *   region: "eu-west-1",
 *   roleArn: "arn:aws:iam::123456789012:role/CrossRegionRole",
 *   roleSessionName: "route-deployment",
 * });
 *
 * @example
 * // Create Route with explicit credentials
 * const explicitCredsRoute = await Route("explicit-creds-route", {
 *   routeTable: testRouteTable,
 *   destinationCidrBlock: "0.0.0.0/0",
 *   target: { internetGateway: testIgw },
 *   accessKeyId: alchemy.secret("AKIAIOSFODNN7EXAMPLE"),
 *   secretAccessKey: alchemy.secret("wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"),
 *   region: "us-west-2",
 * });
 *
 * @example
 * // Create a route to a specific instance
 * const instanceRoute = await Route("instance-route", {
 *   routeTable: "rtb-12345678",
 *   destinationCidrBlock: "10.1.0.0/16",
 *   target: { instanceId: "i-12345678" }
 * });
 *
 * @example
 * // Create a route to a network interface
 * const eniRoute = await Route("eni-route", {
 *   routeTable: mainRouteTable,
 *   destinationCidrBlock: "192.168.1.0/24",
 *   target: { networkInterfaceId: "eni-12345678" }
 * });
 *
 * @example
 * // Create a route to a VPC peering connection
 * const peeringRoute = await Route("peering-route", {
 *   routeTable: mainRouteTable,
 *   destinationCidrBlock: "10.2.0.0/16",
 *   target: { vpcPeeringConnectionId: "pcx-12345678" }
 * });
 *
 * @example
 * // Create a route to a Transit Gateway
 * const transitRoute = await Route("transit-route", {
 *   routeTable: mainRouteTable,
 *   destinationCidrBlock: "10.3.0.0/16",
 *   target: { transitGatewayId: "tgw-12345678" }
 * });
 *
 * @example
 * // Create a route with custom timeout configuration
 * const customRoute = await Route("custom-route", {
 *   routeTable: mainRouteTable,
 *   destinationCidrBlock: "172.16.0.0/16",
 *   target: { internetGateway: "igw-12345678" },
 *   timeout: {
 *     maxAttempts: 60,
 *     delayMs: 2000
 *   }
 * });
 *
 * @example
 * // Multi-account deployment with scope-level and resource-level overrides
 * await alchemy.run("production", {
 *   aws: { region: "us-west-2", profile: "main-account" }
 * }, async () => {
 *   // This Route uses scope credentials (main-account, us-west-2)
 *   const mainRoute = await Route("main-route", {
 *     routeTable: mainRouteTable,
 *     destinationCidrBlock: "0.0.0.0/0",
 *     target: { internetGateway: mainIgw }
 *   });
 *
 *   // This Route overrides to use different account
 *   const crossAccountRoute = await Route("cross-account-route", {
 *     routeTable: crossRouteTable,
 *     destinationCidrBlock: "0.0.0.0/0",
 *     target: { internetGateway: crossIgw },
 *     profile: "secondary-account",
 *     region: "us-east-1", // Also override region
 *   });
 * });
 */
export const Route = Resource("aws::Route", async function (_id, props) {
    // Create EC2 client with credential resolution handled internally
    const client = await createEC2Client(props);
    const _timeoutConfig = mergeTimeoutConfig(ROUTE_TIMEOUT, props.timeout);
    const routeTableId = typeof props.routeTable === "string"
        ? props.routeTable
        : props.routeTable.routeTableId;
    if (this.phase === "delete") {
        if (this.output?.routeTableId) {
            logger.log(`🗑️ Deleting Route: ${this.output.destinationCidrBlock} from Route Table ${this.output.routeTableId}`);
            await retry(async () => {
                await ignore("InvalidRoute.NotFound", async () => {
                    await callEC2Api(client, "DeleteRoute", parseRouteXmlResponse, {
                        RouteTableId: this.output.routeTableId,
                        DestinationCidrBlock: this.output.destinationCidrBlock,
                    });
                });
            });
            // Wait for route to be fully deleted
            logger.log(`  Waiting for Route ${this.output.destinationCidrBlock} to be fully deleted...`);
            await waitForRouteDeleted(client, this.output.routeTableId, this.output.destinationCidrBlock, _timeoutConfig);
            logger.log(`  ✅ Route ${this.output.destinationCidrBlock} deletion completed`);
        }
        return this.destroy();
    }
    // Prepare route parameters
    const routeParams = {
        RouteTableId: routeTableId,
        DestinationCidrBlock: props.destinationCidrBlock,
    };
    // Set the target based on the type
    if ("internetGateway" in props.target) {
        const igwId = typeof props.target.internetGateway === "string"
            ? props.target.internetGateway
            : props.target.internetGateway.internetGatewayId;
        routeParams.GatewayId = igwId;
    }
    else if ("natGateway" in props.target) {
        const natId = typeof props.target.natGateway === "string"
            ? props.target.natGateway
            : props.target.natGateway.natGatewayId;
        routeParams.NatGatewayId = natId;
    }
    else if ("instanceId" in props.target) {
        routeParams.InstanceId = props.target.instanceId;
    }
    else if ("networkInterfaceId" in props.target) {
        routeParams.NetworkInterfaceId = props.target.networkInterfaceId;
    }
    else if ("vpcPeeringConnectionId" in props.target) {
        routeParams.VpcPeeringConnectionId = props.target.vpcPeeringConnectionId;
    }
    else if ("transitGatewayId" in props.target) {
        routeParams.TransitGatewayId = props.target.transitGatewayId;
    }
    if (this.phase === "update" && this.output?.routeTableId) {
        // Check if route exists
        const response = await callEC2Api(client, "DescribeRouteTables", parseRouteXmlResponse, {
            "RouteTableId.1": routeTableId,
        });
        const routeTable = response.RouteTables?.[0];
        const existingRoute = routeTable?.Routes?.find((r) => r.DestinationCidrBlock === props.destinationCidrBlock);
        if (existingRoute) {
            // Replace existing route
            await callEC2Api(client, "ReplaceRoute", parseRouteXmlResponse, routeParams);
        }
        else {
            // Create new route
            await callEC2Api(client, "CreateRoute", parseRouteXmlResponse, routeParams);
        }
    }
    else {
        // Create new route
        await callEC2Api(client, "CreateRoute", parseRouteXmlResponse, routeParams);
    }
    // Wait a moment for the route to be fully created
    await new Promise((resolve) => setTimeout(resolve, 2000));
    // Get the created/updated route to return current state
    const response = await callEC2Api(client, "DescribeRouteTables", parseRouteXmlResponse, {
        "RouteTableId.1": routeTableId,
    });
    const routeTable = response.RouteTables?.[0];
    const route = routeTable?.Routes?.find((r) => r.DestinationCidrBlock === props.destinationCidrBlock);
    if (!route) {
        throw new Error("Failed to find created route");
    }
    return {
        routeTableId,
        state: route.State,
        origin: route.Origin,
        ...props,
        routeTable: routeTableId,
    };
});
/**
 * Wait for Route to be deleted
 */
async function waitForRouteDeleted(client, routeTableId, destinationCidrBlock, timeoutConfig) {
    const checkFunction = async () => {
        try {
            const response = await callEC2Api(client, "DescribeRouteTables", parseRouteXmlResponse, {
                "RouteTableId.1": routeTableId,
            });
            const routeTable = response.RouteTables?.[0];
            const route = routeTable?.Routes?.find((r) => r.DestinationCidrBlock === destinationCidrBlock);
            return route;
        }
        catch (error) {
            // If route table is not found, route is definitely deleted
            if (error.code === "InvalidRouteTableID.NotFound" ||
                error.code === "InvalidRouteTable.NotFound") {
                return undefined;
            }
            throw error;
        }
    };
    const isReady = (route) => {
        // Route is deleted if it doesn't exist
        return !route;
    };
    await waitForResourceState(checkFunction, isReady, timeoutConfig, `${routeTableId}/${destinationCidrBlock}`, "Route", "be deleted");
}
/**
 * Route timeout constants
 */
export const ROUTE_TIMEOUT = {
    maxAttempts: 30,
    delayMs: 1000, // 1 second - Routes are fast resources
};
/**
 * Parse XML responses specifically for Route operations
 */
function parseRouteXmlResponse(xmlText) {
    const parser = new XMLParser({
        ignoreAttributes: true,
        parseAttributeValue: true,
        parseTagValue: true,
        trimValues: true,
    });
    const parsed = parser.parse(xmlText);
    const result = {};
    // Parse DescribeRouteTablesResponse
    if (parsed.DescribeRouteTablesResponse) {
        const routeTableSet = parsed.DescribeRouteTablesResponse.routeTableSet;
        if (routeTableSet?.item) {
            const routeTables = Array.isArray(routeTableSet.item)
                ? routeTableSet.item
                : [routeTableSet.item];
            result.RouteTables = routeTables.map((rt) => ({
                RouteTableId: rt.routeTableId,
                VpcId: rt.vpcId,
                Routes: rt.routeSet?.item
                    ? (Array.isArray(rt.routeSet.item)
                        ? rt.routeSet.item
                        : [rt.routeSet.item]).map((route) => ({
                        DestinationCidrBlock: route.destinationCidrBlock,
                        GatewayId: route.gatewayId,
                        InstanceId: route.instanceId,
                        NetworkInterfaceId: route.networkInterfaceId,
                        VpcPeeringConnectionId: route.vpcPeeringConnectionId,
                        NatGatewayId: route.natGatewayId,
                        TransitGatewayId: route.transitGatewayId,
                        State: route.state,
                        Origin: route.origin,
                    }))
                    : [],
            }));
        }
        else {
            result.RouteTables = [];
        }
    }
    // Handle success responses
    if (parsed.CreateRouteResponse ||
        parsed.ReplaceRouteResponse ||
        parsed.DeleteRouteResponse) {
        result.success = true;
    }
    return result;
}
//# sourceMappingURL=route.js.map