import {
  AWSClient
} from "./chunk-zqtpg06c.js";

// src/aws/cloudformation.ts
class CloudFormationClient {
  client;
  region;
  constructor(region = "us-east-1", profile) {
    this.region = region;
    this.client = new AWSClient(undefined, { profile });
  }
  async createStack(options) {
    const params = {
      Action: "CreateStack",
      StackName: options.stackName,
      Version: "2010-05-15"
    };
    if (options.templateBody) {
      params.TemplateBody = options.templateBody;
    } else if (options.templateUrl) {
      params.TemplateURL = options.templateUrl;
    } else {
      throw new Error("Either templateBody or templateUrl must be provided");
    }
    if (options.parameters) {
      options.parameters.forEach((param, index) => {
        params[`Parameters.member.${index + 1}.ParameterKey`] = param.ParameterKey;
        params[`Parameters.member.${index + 1}.ParameterValue`] = param.ParameterValue;
      });
    }
    if (options.capabilities) {
      options.capabilities.forEach((cap, index) => {
        params[`Capabilities.member.${index + 1}`] = cap;
      });
    }
    if (options.roleArn) {
      params.RoleARN = options.roleArn;
    }
    if (options.tags) {
      options.tags.forEach((tag, index) => {
        params[`Tags.member.${index + 1}.Key`] = tag.Key;
        params[`Tags.member.${index + 1}.Value`] = tag.Value;
      });
    }
    if (options.timeoutInMinutes) {
      params.TimeoutInMinutes = options.timeoutInMinutes;
    }
    if (options.onFailure) {
      params.OnFailure = options.onFailure;
    }
    const result = await this.client.request({
      service: "cloudformation",
      region: this.region,
      method: "POST",
      path: "/",
      body: new URLSearchParams(params).toString()
    });
    return { StackId: result.StackId || result.CreateStackResult?.StackId };
  }
  async updateStack(options) {
    const params = {
      Action: "UpdateStack",
      StackName: options.stackName,
      Version: "2010-05-15"
    };
    if (options.templateBody) {
      params.TemplateBody = options.templateBody;
    } else if (options.templateUrl) {
      params.TemplateURL = options.templateUrl;
    }
    if (options.parameters) {
      options.parameters.forEach((param, index) => {
        params[`Parameters.member.${index + 1}.ParameterKey`] = param.ParameterKey;
        if (param.UsePreviousValue) {
          params[`Parameters.member.${index + 1}.UsePreviousValue`] = "true";
        } else {
          params[`Parameters.member.${index + 1}.ParameterValue`] = param.ParameterValue;
        }
      });
    }
    if (options.capabilities) {
      options.capabilities.forEach((cap, index) => {
        params[`Capabilities.member.${index + 1}`] = cap;
      });
    }
    if (options.roleArn) {
      params.RoleARN = options.roleArn;
    }
    if (options.tags) {
      options.tags.forEach((tag, index) => {
        params[`Tags.member.${index + 1}.Key`] = tag.Key;
        params[`Tags.member.${index + 1}.Value`] = tag.Value;
      });
    }
    const result = await this.client.request({
      service: "cloudformation",
      region: this.region,
      method: "POST",
      path: "/",
      body: new URLSearchParams(params).toString()
    });
    return { StackId: result.StackId || result.UpdateStackResult?.StackId };
  }
  async deleteStack(stackName, roleArn, retainResources) {
    const params = {
      Action: "DeleteStack",
      StackName: stackName,
      Version: "2010-05-15"
    };
    if (roleArn) {
      params.RoleARN = roleArn;
    }
    if (retainResources && retainResources.length > 0) {
      retainResources.forEach((resource, index) => {
        params[`RetainResources.member.${index + 1}`] = resource;
      });
    }
    await this.client.request({
      service: "cloudformation",
      region: this.region,
      method: "POST",
      path: "/",
      body: new URLSearchParams(params).toString()
    });
  }
  async describeStacks(options = {}) {
    const params = {
      Action: "DescribeStacks",
      Version: "2010-05-15"
    };
    if (options.stackName) {
      params.StackName = options.stackName;
    }
    const result = await this.client.request({
      service: "cloudformation",
      region: this.region,
      method: "POST",
      path: "/",
      body: new URLSearchParams(params).toString()
    });
    const stacks = this.parseStacksResponse(result);
    return { Stacks: stacks };
  }
  async describeStackEvents(stackName) {
    const params = {
      Action: "DescribeStackEvents",
      StackName: stackName,
      Version: "2010-05-15"
    };
    const result = await this.client.request({
      service: "cloudformation",
      region: this.region,
      method: "POST",
      path: "/",
      body: new URLSearchParams(params).toString()
    });
    return { StackEvents: this.parseStackEvents(result) };
  }
  async listStackResources(stackName) {
    const params = {
      Action: "ListStackResources",
      StackName: stackName,
      Version: "2010-05-15"
    };
    const result = await this.client.request({
      service: "cloudformation",
      region: this.region,
      method: "POST",
      path: "/",
      body: new URLSearchParams(params).toString()
    });
    const member = result?.ListStackResourcesResult?.StackResourceSummaries?.member;
    let resources = [];
    if (member) {
      resources = Array.isArray(member) ? member : [member];
    }
    return { StackResourceSummaries: resources };
  }
  async waitForStack(stackName, waitType) {
    const targetStatuses = {
      "stack-create-complete": ["CREATE_COMPLETE"],
      "stack-update-complete": ["UPDATE_COMPLETE"],
      "stack-delete-complete": ["DELETE_COMPLETE"]
    };
    const failureStatuses = [
      "CREATE_FAILED",
      "ROLLBACK_FAILED",
      "ROLLBACK_COMPLETE",
      "UPDATE_ROLLBACK_FAILED",
      "UPDATE_ROLLBACK_COMPLETE"
    ];
    const targets = targetStatuses[waitType];
    const maxAttempts = 360;
    let attempts = 0;
    while (attempts < maxAttempts) {
      try {
        const result = await this.describeStacks({ stackName });
        if (result.Stacks.length === 0) {
          if (waitType === "stack-delete-complete") {
            return;
          }
          if (attempts % 10 === 0) {
            console.log(`[waitForStack] Attempt ${attempts}: Stack not visible yet`);
          }
          await new Promise((resolve) => setTimeout(resolve, 2000));
          attempts++;
          continue;
        }
        const stack = result.Stacks[0];
        if (attempts % 10 === 0) {
          console.log(`[waitForStack] Attempt ${attempts}: Status = ${stack.StackStatus}${stack.StackStatusReason ? ` (${stack.StackStatusReason})` : ""}`);
        }
        if (targets.includes(stack.StackStatus)) {
          return;
        }
        if ((waitType === "stack-create-complete" || waitType === "stack-update-complete") && (stack.StackStatus === "DELETE_IN_PROGRESS" || stack.StackStatus === "DELETE_COMPLETE")) {
          console.log(`[waitForStack] Stack is being deleted (creation/update failed)`);
          let failedEventReason = "";
          try {
            const eventsResult = await this.describeStackEvents(stackName);
            console.log("[waitForStack] Stack events (most recent first):");
            for (const event of eventsResult.StackEvents.slice(0, 15)) {
              if (event.ResourceStatus.includes("FAILED") || event.ResourceStatusReason) {
                console.log(`  ${event.LogicalResourceId}: ${event.ResourceStatus} - ${event.ResourceStatusReason || "No reason provided"}`);
                if (event.ResourceStatus.includes("FAILED") && event.ResourceStatusReason && !failedEventReason) {
                  failedEventReason = event.ResourceStatusReason;
                }
              }
            }
          } catch {}
          const errorReason = failedEventReason || stack.StackStatusReason || "Check CloudFormation console for details.";
          throw new Error(`Stack creation/update failed - stack is being deleted. Reason: ${errorReason}`);
        }
        if (stack.StackStatus === "DELETE_FAILED" && waitType === "stack-delete-complete") {
          const error = new Error(`Stack deletion failed - may have resources that need to be retained`);
          error.code = "DELETE_FAILED";
          error.stackStatus = stack.StackStatus;
          error.statusReason = stack.StackStatusReason;
          throw error;
        }
        if (failureStatuses.includes(stack.StackStatus)) {
          throw new Error(`Stack reached failure status: ${stack.StackStatus}`);
        }
        await new Promise((resolve) => setTimeout(resolve, 5000));
        attempts++;
      } catch (error) {
        if (waitType === "stack-delete-complete" && error.message?.includes("does not exist")) {
          return;
        }
        if (waitType === "stack-create-complete" && error.message?.includes("does not exist")) {
          if (attempts % 10 === 0) {
            console.log(`[waitForStack] Attempt ${attempts}: Stack does not exist (error), retrying...`);
          }
          await new Promise((resolve) => setTimeout(resolve, 2000));
          attempts++;
          continue;
        }
        console.log(`[waitForStack] Unexpected error: ${error.message}`);
        throw error;
      }
    }
    console.log(`[waitForStack] Timeout after ${attempts} attempts`);
    throw new Error(`Timeout waiting for stack to reach ${waitType}`);
  }
  async validateTemplate(templateBody) {
    const params = {
      Action: "ValidateTemplate",
      TemplateBody: templateBody,
      Version: "2010-05-15"
    };
    return await this.client.request({
      service: "cloudformation",
      region: this.region,
      method: "POST",
      path: "/",
      body: new URLSearchParams(params).toString()
    });
  }
  async listStacks(statusFilter) {
    const params = {
      Action: "ListStacks",
      Version: "2010-05-15"
    };
    if (statusFilter) {
      statusFilter.forEach((status, index) => {
        params[`StackStatusFilter.member.${index + 1}`] = status;
      });
    }
    const result = await this.client.request({
      service: "cloudformation",
      region: this.region,
      method: "POST",
      path: "/",
      body: new URLSearchParams(params).toString()
    });
    const response = result.ListStacksResponse || result;
    const summariesResult = response.ListStacksResult || response;
    const members = summariesResult.StackSummaries?.member || summariesResult.StackSummaries || [];
    const items = Array.isArray(members) ? members : members ? [members] : [];
    return {
      StackSummaries: items.map((s) => ({
        StackId: s.StackId || "",
        StackName: s.StackName || "",
        TemplateDescription: s.TemplateDescription,
        CreationTime: s.CreationTime || "",
        LastUpdatedTime: s.LastUpdatedTime,
        DeletionTime: s.DeletionTime,
        StackStatus: s.StackStatus || ""
      }))
    };
  }
  async createChangeSet(options) {
    const params = {
      Action: "CreateChangeSet",
      StackName: options.stackName,
      ChangeSetName: options.changeSetName,
      Version: "2010-05-15"
    };
    if (options.templateBody) {
      params.TemplateBody = options.templateBody;
    } else if (options.templateUrl) {
      params.TemplateURL = options.templateUrl;
    }
    if (options.parameters) {
      options.parameters.forEach((param, index) => {
        params[`Parameters.member.${index + 1}.ParameterKey`] = param.ParameterKey;
        params[`Parameters.member.${index + 1}.ParameterValue`] = param.ParameterValue;
      });
    }
    if (options.capabilities) {
      options.capabilities.forEach((cap, index) => {
        params[`Capabilities.member.${index + 1}`] = cap;
      });
    }
    if (options.changeSetType) {
      params.ChangeSetType = options.changeSetType;
    }
    const result = await this.client.request({
      service: "cloudformation",
      region: this.region,
      method: "POST",
      path: "/",
      body: new URLSearchParams(params).toString()
    });
    return { Id: result.Id, StackId: result.StackId };
  }
  async describeChangeSet(stackName, changeSetName) {
    const params = {
      Action: "DescribeChangeSet",
      StackName: stackName,
      ChangeSetName: changeSetName,
      Version: "2010-05-15"
    };
    return await this.client.request({
      service: "cloudformation",
      region: this.region,
      method: "POST",
      path: "/",
      body: new URLSearchParams(params).toString()
    });
  }
  async executeChangeSet(stackName, changeSetName) {
    const params = {
      Action: "ExecuteChangeSet",
      StackName: stackName,
      ChangeSetName: changeSetName,
      Version: "2010-05-15"
    };
    await this.client.request({
      service: "cloudformation",
      region: this.region,
      method: "POST",
      path: "/",
      body: new URLSearchParams(params).toString()
    });
  }
  async deleteChangeSet(stackName, changeSetName) {
    const params = {
      Action: "DeleteChangeSet",
      StackName: stackName,
      ChangeSetName: changeSetName,
      Version: "2010-05-15"
    };
    await this.client.request({
      service: "cloudformation",
      region: this.region,
      method: "POST",
      path: "/",
      body: new URLSearchParams(params).toString()
    });
  }
  async getStackOutputs(stackName) {
    const result = await this.describeStacks({ stackName });
    if (!result.Stacks || result.Stacks.length === 0) {
      throw new Error(`Stack ${stackName} not found`);
    }
    const stack = result.Stacks[0];
    const outputs = {};
    if (stack.Outputs) {
      for (const output of stack.Outputs) {
        outputs[output.OutputKey] = output.OutputValue;
      }
    }
    return outputs;
  }
  async getTemplate(stackName) {
    const params = {
      Action: "GetTemplate",
      StackName: stackName,
      Version: "2010-05-15"
    };
    const result = await this.client.request({
      service: "cloudformation",
      region: this.region,
      method: "POST",
      path: "/",
      body: new URLSearchParams(params).toString()
    });
    const templateBody = result?.GetTemplateResult?.TemplateBody || result?.TemplateBody || "";
    return { TemplateBody: templateBody };
  }
  parseStacksResponse(result) {
    const stacks = [];
    let stackData = result?.DescribeStacksResult?.Stacks?.member || result?.Stacks?.member || result?.Stacks || result;
    if (stackData && !Array.isArray(stackData)) {
      stackData = [stackData];
    }
    if (Array.isArray(stackData)) {
      for (const s of stackData) {
        if (s.StackId || s.StackName) {
          let outputs;
          if (s.Outputs?.member) {
            const outputData = Array.isArray(s.Outputs.member) ? s.Outputs.member : [s.Outputs.member];
            outputs = outputData.map((o) => ({
              OutputKey: o.OutputKey,
              OutputValue: o.OutputValue,
              Description: o.Description,
              ExportName: o.ExportName
            }));
          }
          stacks.push({
            StackId: s.StackId,
            StackName: s.StackName,
            StackStatus: s.StackStatus,
            CreationTime: s.CreationTime,
            LastUpdatedTime: s.LastUpdatedTime,
            StackStatusReason: s.StackStatusReason,
            Outputs: outputs
          });
        }
      }
    } else if (result.StackId) {
      stacks.push({
        StackId: result.StackId,
        StackName: result.StackName,
        StackStatus: result.StackStatus,
        CreationTime: result.CreationTime,
        LastUpdatedTime: result.LastUpdatedTime
      });
    }
    return stacks;
  }
  parseStackEvents(result) {
    const events = [];
    let eventData = result?.DescribeStackEventsResult?.StackEvents?.member || result?.StackEvents?.member || result?.StackEvents || [];
    if (eventData && !Array.isArray(eventData)) {
      eventData = [eventData];
    }
    for (const e of eventData) {
      if (e.LogicalResourceId) {
        events.push({
          Timestamp: e.Timestamp,
          ResourceType: e.ResourceType,
          LogicalResourceId: e.LogicalResourceId,
          ResourceStatus: e.ResourceStatus,
          ResourceStatusReason: e.ResourceStatusReason
        });
      }
    }
    return events;
  }
  async waitForStackWithProgress(stackName, waitType, onProgress) {
    const targetStatuses = {
      "stack-create-complete": ["CREATE_COMPLETE"],
      "stack-update-complete": ["UPDATE_COMPLETE"],
      "stack-delete-complete": ["DELETE_COMPLETE"]
    };
    const failureStatuses = [
      "CREATE_FAILED",
      "ROLLBACK_FAILED",
      "ROLLBACK_COMPLETE",
      "UPDATE_ROLLBACK_FAILED",
      "UPDATE_ROLLBACK_COMPLETE"
    ];
    const targets = targetStatuses[waitType];
    const maxAttempts = 360;
    let attempts = 0;
    const seenEvents = new Set;
    while (attempts < maxAttempts) {
      try {
        if (onProgress) {
          try {
            const eventsResult = await this.describeStackEvents(stackName);
            const events = [...eventsResult.StackEvents || []].reverse();
            for (const event of events) {
              const eventKey = `${event.LogicalResourceId}-${event.ResourceStatus}-${event.Timestamp}`;
              if (!seenEvents.has(eventKey)) {
                seenEvents.add(eventKey);
                onProgress({
                  resourceId: event.LogicalResourceId,
                  resourceType: event.ResourceType,
                  status: event.ResourceStatus,
                  reason: event.ResourceStatusReason,
                  timestamp: event.Timestamp
                });
              }
            }
          } catch {}
        }
        const result = await this.describeStacks({ stackName });
        if (result.Stacks.length === 0) {
          if (waitType === "stack-delete-complete") {
            return;
          }
          await new Promise((resolve) => setTimeout(resolve, 2000));
          attempts++;
          continue;
        }
        const stack = result.Stacks[0];
        if (targets.includes(stack.StackStatus)) {
          return;
        }
        if (stack.StackStatus === "DELETE_FAILED" && waitType === "stack-delete-complete") {
          const error = new Error(`Stack deletion failed - may have resources that need to be retained`);
          error.code = "DELETE_FAILED";
          error.stackStatus = stack.StackStatus;
          error.statusReason = stack.StackStatusReason;
          throw error;
        }
        if (failureStatuses.includes(stack.StackStatus)) {
          throw new Error(`Stack reached failure status: ${stack.StackStatus}`);
        }
        await new Promise((resolve) => setTimeout(resolve, 3000));
        attempts++;
      } catch (error) {
        if (waitType === "stack-delete-complete" && error.message?.includes("does not exist")) {
          return;
        }
        if (waitType === "stack-create-complete" && error.message?.includes("does not exist")) {
          await new Promise((resolve) => setTimeout(resolve, 2000));
          attempts++;
          continue;
        }
        throw error;
      }
    }
    throw new Error(`Timeout waiting for stack to reach ${waitType}`);
  }
  async waitForStackComplete(stackName, maxAttempts = 120, delayMs = 5000) {
    const successStatuses = ["CREATE_COMPLETE", "UPDATE_COMPLETE", "DELETE_COMPLETE"];
    const failureStatuses = [
      "CREATE_FAILED",
      "UPDATE_FAILED",
      "DELETE_FAILED",
      "ROLLBACK_COMPLETE",
      "ROLLBACK_FAILED",
      "UPDATE_ROLLBACK_COMPLETE",
      "UPDATE_ROLLBACK_FAILED"
    ];
    for (let i = 0;i < maxAttempts; i++) {
      try {
        const result = await this.describeStacks({ stackName });
        if (result.Stacks.length === 0) {
          return { success: true, status: "DELETE_COMPLETE" };
        }
        const stack = result.Stacks[0];
        const status = stack.StackStatus;
        if (successStatuses.includes(status)) {
          return { success: true, status };
        }
        if (failureStatuses.includes(status)) {
          return { success: false, status, reason: stack.StackStatusReason };
        }
        await new Promise((resolve) => setTimeout(resolve, delayMs));
      } catch (error) {
        if (error.message?.includes("does not exist")) {
          return { success: true, status: "DELETE_COMPLETE" };
        }
        throw error;
      }
    }
    return { success: false, status: "TIMEOUT", reason: "Timeout waiting for stack operation" };
  }
}

export { CloudFormationClient };
