import { TerraformProviderConfig, OpenAPISpec } from "./Types";
import { FileGenerator } from "./FileGenerator";
import { StringUtils } from "./StringUtils";
import { OpenAPIParser } from "./OpenAPIParser";

export class DocumentationGenerator {
  private config: TerraformProviderConfig;
  private spec: OpenAPISpec;
  private fileGenerator: FileGenerator;

  public constructor(config: TerraformProviderConfig, spec: OpenAPISpec) {
    this.config = config;
    this.spec = spec;
    this.fileGenerator = new FileGenerator(config.outputDir);
  }

  public async generateDocumentation(): Promise<void> {
    await this.generateProviderDoc();
    await this.generateResourceDocs();
    await this.generateDataSourceDocs();
    await this.generateExamples();
    await this.generateReadme();
  }

  private async generateProviderDoc(): Promise<void> {
    const providerDoc: string = `---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "${this.config.providerName} Provider"
subcategory: ""
description: |-
  Terraform provider for ${StringUtils.capitalize(this.config.providerName)}.
---

# ${StringUtils.capitalize(this.config.providerName)} Provider

${this.spec.info.description || `Terraform provider for ${StringUtils.capitalize(this.config.providerName)}.`}

## Example Usage

\`\`\`terraform
terraform {
  required_providers {
    ${this.config.providerName} = {
      source = "oneuptime/${this.config.providerName}"
      version = "${this.config.providerVersion}"
    }
  }
}

provider "${this.config.providerName}" {
  oneuptime_url = "oneuptime.com"  # Optional, defaults to oneuptime.com (internally becomes oneuptime.com/api)
  api_key       = var.${this.config.providerName}_api_key
}
\`\`\`

## Schema

### Required

- \`api_key\` (String, Sensitive) API key for authentication. Can also be set via the \`${StringUtils.toConstantCase(this.config.providerName)}_API_KEY\` environment variable.

### Optional

- \`oneuptime_url\` (String) The ${this.config.providerName} URL (without /api path). Defaults to 'oneuptime.com' if not specified. The provider automatically appends '/api' to the URL. Can also be set via the \`${StringUtils.toConstantCase(this.config.providerName)}_URL\` environment variable.
`;

    await this.fileGenerator.writeFileInDir("docs", "index.md", providerDoc);
  }

  private async generateResourceDocs(): Promise<void> {
    // Create parser and get resources
    const parser: OpenAPIParser = new OpenAPIParser();
    parser.setSpec(this.spec);
    const resources: any[] = parser.getResources();

    this.fileGenerator.ensureDirectory("docs/resources");

    for (const resource of resources) {
      const resourceDoc: string = this.generateResourceDoc(resource);
      await this.fileGenerator.writeFileInDir(
        "docs/resources",
        `${resource.name}.md`,
        resourceDoc,
      );
    }
  }

  private async generateDataSourceDocs(): Promise<void> {
    // Create parser and get data sources
    const parser: OpenAPIParser = new OpenAPIParser();
    parser.setSpec(this.spec);
    const dataSources: any[] = parser.getDataSources();

    this.fileGenerator.ensureDirectory("docs/data-sources");

    for (const dataSource of dataSources) {
      const dataSourceDoc: string = this.generateDataSourceDoc(dataSource);
      await this.fileGenerator.writeFileInDir(
        "docs/data-sources",
        `${dataSource.name}.md`,
        dataSourceDoc,
      );
    }
  }

  private generateResourceDoc(resource: any): string {
    const resourceName: string = StringUtils.capitalize(
      resource.name.replace(/_/g, " "),
    );

    // Generate example based on required fields
    const exampleFields: string = this.generateExampleFields(resource);

    // Generate schema documentation
    const schemaItems: string[] = [];
    for (const [name, attr] of Object.entries(resource.schema)) {
      const attrInfo: any = attr as any;
      const required: string = attrInfo.required
        ? "Required"
        : attrInfo.computed
          ? "Computed"
          : "Optional";
      const sensitive: string = attrInfo.sensitive ? ", Sensitive" : "";
      schemaItems.push(
        `- \`${name}\` (${StringUtils.capitalize(attrInfo.type)}${sensitive}) ${attrInfo.description || `${resourceName} ${name}`}. ${required}.`,
      );
    }

    return `---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "${this.config.providerName}_${resource.name} Resource - ${this.config.providerName}"
subcategory: ""
description: |-
  ${resourceName} resource
---

# ${this.config.providerName}_${resource.name} (Resource)

${resourceName} resource

## Example Usage

\`\`\`terraform
resource "${this.config.providerName}_${resource.name}" "example" {
${exampleFields}
}
\`\`\`

## Schema

${schemaItems.join("\n")}

## Import

Import is supported using the following syntax:

\`\`\`shell
terraform import ${this.config.providerName}_${resource.name}.example <id>
\`\`\`
`;
  }

  private generateExampleFields(resource: any): string {
    const fields: string[] = [];

    // Add required fields first
    for (const [name, attr] of Object.entries(resource.schema)) {
      const attrInfo: any = attr as any;
      if (attrInfo.required && name !== "id") {
        const exampleValue: string = this.getExampleValue(name, attrInfo);
        fields.push(`  ${name} = ${exampleValue}`);
      }
    }

    // Add some common optional fields for better examples
    for (const [name, attr] of Object.entries(resource.schema)) {
      const attrInfo: any = attr as any;
      if (
        !attrInfo.required &&
        !attrInfo.computed &&
        ["name", "description"].includes(name)
      ) {
        const exampleValue: string = this.getExampleValue(name, attrInfo);
        fields.push(`  ${name} = ${exampleValue}`);
      }
    }

    return fields.join("\n");
  }

  private getExampleValue(fieldName: string, attrInfo: any): string {
    // First, try to use the example from OpenAPI spec
    if (attrInfo.example !== undefined && attrInfo.example !== null) {
      return this.formatOpenAPIExample(attrInfo.example, attrInfo.type);
    }

    // Fallback to the existing hardcoded logic
    // Handle specific field types and names
    if (fieldName.includes("id") && attrInfo.type === "string") {
      return '"123e4567-e89b-12d3-a456-426614174000"';
    }

    if (fieldName === "name") {
      return `"example-${this.getResourceNameFromSchema(attrInfo) || "resource"}"`;
    }

    if (fieldName === "description") {
      return `"Example ${this.getResourceNameFromSchema(attrInfo) || "resource"}"`;
    }

    if (fieldName === "color" && attrInfo.type === "map") {
      return `{\n    _type = "Color"\n    value = "#ff0000"\n  }`;
    }

    if (attrInfo.type === "map" || attrInfo.type === "object") {
      return `{\n    id = "123e4567-e89b-12d3-a456-426614174000"\n  }`;
    }

    switch (attrInfo.type) {
      case "string":
        return `"example-${fieldName}"`;
      case "number":
        return "1";
      case "boolean":
        return "true";
      case "list":
        return "[]";
      default:
        return `"example-${fieldName}"`;
    }
  }

  private getResourceNameFromSchema(attrInfo: any): string | null {
    // Try to extract resource name from description or context
    if (attrInfo.description) {
      const desc: string = attrInfo.description.toLowerCase();
      // Look for patterns like "Example label" or "Label name"
      const match: RegExpMatchArray | null = desc.match(
        /example (\w+)|(\w+) name|(\w+) description/,
      );
      if (match) {
        return match[1] || match[2] || match[3] || null;
      }
    }
    return null;
  }

  private generateDataSourceDoc(dataSource: any): string {
    const dataSourceName: string = StringUtils.capitalize(
      dataSource.name.replace(/_/g, " "),
    );

    // Generate schema documentation
    const schemaItems: string[] = [];
    for (const [name, attr] of Object.entries(dataSource.schema)) {
      const attrInfo: any = attr as any;
      const required: string = attrInfo.required
        ? "Required"
        : attrInfo.computed
          ? "Computed"
          : "Optional";
      const sensitive: string = attrInfo.sensitive ? ", Sensitive" : "";
      schemaItems.push(
        `- \`${name}\` (${StringUtils.capitalize(attrInfo.type)}${sensitive}) ${attrInfo.description || `${dataSourceName} ${name}`}. ${required}.`,
      );
    }

    return `---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "${this.config.providerName}_${dataSource.name} Data Source - ${this.config.providerName}"
subcategory: ""
description: |-
  ${dataSourceName} data source
---

# ${this.config.providerName}_${dataSource.name} (Data Source)

${dataSourceName} data source

## Example Usage

\`\`\`terraform
data "${this.config.providerName}_${dataSource.name}" "example" {
  name = "example-${dataSource.name}"
}
\`\`\`

## Schema

${schemaItems.join("\n")}
`;
  }

  private async generateExamples(): Promise<void> {
    this.fileGenerator.ensureDirectory("examples");

    // Generate provider example
    const providerExample: string = `terraform {
  required_providers {
    ${this.config.providerName} = {
      source = "oneuptime/${this.config.providerName}"
      version = "${this.config.providerVersion}"
    }
  }
}

provider "${this.config.providerName}" {
  host    = "oneuptime.com"  # Optional, defaults to oneuptime.com (provider appends /api automatically)
  api_key = var.${this.config.providerName}_api_key
}

# Configure variables
variable "${this.config.providerName}_api_key" {
  description = "API key for ${this.config.providerName}"
  type        = string
  sensitive   = true
}
`;

    await this.fileGenerator.writeFileInDir(
      "examples",
      "provider.tf",
      providerExample,
    );

    // Generate resources example
    const parser: OpenAPIParser = new OpenAPIParser();
    parser.setSpec(this.spec);
    const resources: any[] = parser.getResources();

    if (resources.length > 0) {
      const firstResource: any = resources[0];
      if (firstResource) {
        const resourceExample: string = `# Example usage of ${this.config.providerName}_${firstResource.name} resource
resource "${this.config.providerName}_${firstResource.name}" "example" {
  name        = "example-${firstResource.name}"
  description = "Example ${firstResource.name} created by Terraform"
}

# Output the resource ID
output "${firstResource.name}_id" {
  description = "ID of the created ${firstResource.name}"
  value       = ${this.config.providerName}_${firstResource.name}.example.id
}
`;

        await this.fileGenerator.writeFileInDir(
          "examples",
          "resources.tf",
          resourceExample,
        );
      }
    }

    // Generate data sources example
    const dataSources: any[] = parser.getDataSources();

    if (dataSources.length > 0) {
      const firstDataSource: any = dataSources[0];
      if (firstDataSource) {
        const dataSourceExample: string = `# Example usage of ${this.config.providerName}_${firstDataSource.name} data source
data "${this.config.providerName}_${firstDataSource.name}" "example" {
  name = "example-${firstDataSource.name}"
}

# Output the data source result
output "${firstDataSource.name}_result" {
  description = "Result of the ${firstDataSource.name} data source"
  value       = data.${this.config.providerName}_${firstDataSource.name}.example
}
`;

        await this.fileGenerator.writeFileInDir(
          "examples",
          "data-sources.tf",
          dataSourceExample,
        );
      }
    }
  }

  private async generateReadme(): Promise<void> {
    const readmeContent: string = `# Terraform Provider for ${StringUtils.capitalize(this.config.providerName)}

${this.spec.info.description || `Terraform provider for ${StringUtils.capitalize(this.config.providerName)}.`}

## Requirements

- [Terraform](https://www.terraform.io/downloads.html) >= 1.0
- [Go](https://golang.org/doc/install) >= 1.21

## Building The Provider

1. Clone the repository
\`\`\`sh
git clone https://github.com/oneuptime/terraform-provider-${this.config.providerName}
cd terraform-provider-${this.config.providerName}
\`\`\`

2. Build the provider using the Go \`install\` command:
\`\`\`sh
go build
\`\`\`

## Using the Provider

\`\`\`terraform
terraform {
  required_providers {
    ${this.config.providerName} = {
      source = "oneuptime/${this.config.providerName}"
      version = "${this.config.providerVersion}"
    }
  }
}

provider "${this.config.providerName}" {
  oneuptime_url = "https://api.${this.config.providerName}.com"
  api_key       = var.${this.config.providerName}_api_key
}
\`\`\`

## Developing the Provider

If you wish to work on the provider, you'll first need [Go](http://www.golang.org) installed on your machine (see [Requirements](#requirements) above).

To compile the provider, run \`go build\`. This will build the provider and put the provider binary in the current directory.

To generate or update documentation, run \`go generate\`.

In order to run the full suite of Acceptance tests, run \`make testacc\`.

*Note:* Acceptance tests create real resources, and often cost money to run.

\`\`\`sh
make testacc
\`\`\`

## Local Installation

To install the provider locally for testing:

\`\`\`sh
make install
\`\`\`

This will build and install the provider to your local Terraform plugins directory.

## Testing

To run unit tests:

\`\`\`sh
go test ./...
\`\`\`

To run acceptance tests:

\`\`\`sh
TF_ACC=1 go test ./... -v -timeout 120m
\`\`\`

## Documentation

Documentation is generated using [terraform-plugin-docs](https://github.com/hashicorp/terraform-plugin-docs). Run the following command to generate documentation:

\`\`\`sh
go generate
\`\`\`

## Contributing

1. This is a read-only repository. The source code is generated from the OneUptime OpenAPI specification. You can check the main repository at [OneUptime](https://github.com/oneuptime/oneuptime). Please fork the main repository and make changes there.
2. Create your feature branch (\`git checkout -b feature/amazing-feature\`)
3. Commit your changes (\`git commit -am 'Add some amazing feature'\`)
4. Push to the branch (\`git push origin feature/amazing-feature\`)
5. Open a Pull Request

## License

This project is licensed under the Apache 2.0 License - see the [LICENSE](LICENSE) file for details.
`;

    await this.fileGenerator.writeFile("README.md", readmeContent);
  }

  private formatOpenAPIExample(example: any, _fieldType?: string): string {
    if (example === null || example === undefined) {
      return '""';
    }

    // Handle different types of examples
    if (typeof example === "string") {
      return `"${example}"`;
    }

    if (typeof example === "number") {
      return example.toString();
    }

    if (typeof example === "boolean") {
      return example.toString();
    }

    if (Array.isArray(example)) {
      if (example.length === 0) {
        return "[]";
      }
      const items: string[] = example.map((item: any) => {
        return this.formatOpenAPIExample(item, "string");
      });
      const itemsString: string = items.join(", ");
      return `[${itemsString}]`;
    }

    if (typeof example === "object") {
      // Handle special OneUptime object types
      if (example._type && example.value !== undefined) {
        switch (example._type) {
          case "DateTime":
            return `"${example.value}"`;
          case "Name":
          case "Email":
          case "Phone":
            return `"${example.value}"`;
          case "Color":
            return `"${example.value}"`;
          default:
            return `"${example.value}"`;
        }
      }

      // Handle generic objects as maps
      const entries: [string, any][] = Object.entries(example);
      const entryStrings: string[] = entries.map(
        ([key, value]: [string, any]) => {
          return `    ${key} = ${this.formatOpenAPIExample(value, "string")}`;
        },
      );
      const entriesString: string = entryStrings.join("\n");
      return `{\n${entriesString}\n  }`;
    }

    // Fallback to string representation
    return `"${String(example)}"`;
  }
}
