/**
 * Drizzle ORM Database Generator
 * Generates Drizzle schemas and migration files from workflow definitions
 */

import { DatabaseGenerator, GeneratorContext, GenerationResult, GeneratedFile } from '../types';
import * as path from 'path';

export class DrizzleGenerator implements DatabaseGenerator {
  id = 'drizzle-database';
  name = 'Drizzle ORM Database Generator';
  description = 'Generates Drizzle schemas and migrations';
  database = 'drizzle';

  generate(context: GeneratorContext): GenerationResult {
    const { workflow, extendedWorkflow, outputDirectory } = context;
    const files: GeneratedFile[] = [];

    // Check if extended workflow has models (new format)
    if (extendedWorkflow?.models && Object.keys(extendedWorkflow.models).length > 0) {
      // Use extended workflow format
      const provider = this.extractProviderFromExtended(extendedWorkflow);
      const componentName = (extendedWorkflow.technology?.database as any)?.directory || 'database';

      files.push(this.generateDrizzleSchemaFromModels(extendedWorkflow.models, provider, outputDirectory, componentName));
      files.push(this.generateDatabaseClient(componentName, provider, outputDirectory));
      files.push(this.generateMigrationScript(componentName, outputDirectory));
      files.push(this.generateDrizzleConfig(componentName, provider, outputDirectory));
      files.push(this.generatePackageJson(componentName, provider, outputDirectory));
      files.push(this.generateEnvExample(componentName, provider, outputDirectory));
      files.push(this.generateReadme(componentName, provider, outputDirectory));

      return { files };
    }

    // Fall back to legacy stack format
    const databaseComponents = this.getDatabaseComponents(workflow);

    if (databaseComponents.length === 0) {
      return { files: [] };
    }

    // Generate for each database component
    for (const [componentName, component] of databaseComponents) {
      // Determine database provider from technology
      const provider = this.extractProvider(component.technology);

      // Generate Drizzle schema
      files.push(this.generateDrizzleSchema(componentName, component, provider, outputDirectory));

      // Generate database client
      files.push(this.generateDatabaseClient(componentName, provider, outputDirectory));

      // Generate migration script
      files.push(this.generateMigrationScript(componentName, outputDirectory));

      // Generate drizzle config
      files.push(this.generateDrizzleConfig(componentName, provider, outputDirectory));

      // Generate package.json with Drizzle dependencies
      files.push(this.generatePackageJson(componentName, provider, outputDirectory));

      // Generate .env.example
      files.push(this.generateEnvExample(componentName, provider, outputDirectory));

      // Generate README
      files.push(this.generateReadme(componentName, provider, outputDirectory));
    }

    return { files };
  }

  private getDatabaseComponents(workflow: any): Array<[string, any]> {
    const components: Array<[string, any]> = [];
    for (const [name, component] of Object.entries(workflow.stack)) {
      const comp = component as any;
      if (comp.type === 'database' && this.supportsTechnology(comp.technology)) {
        components.push([name, comp]);
      }
    }
    return components;
  }

  private supportsTechnology(technology: string): boolean {
    return technology.includes('drizzle');
  }

  private extractProvider(technology: string): string {
    // Extract database type from "postgresql/drizzle"
    const baseDb = technology.split('/')[0];

    const providerMap: Record<string, string> = {
      postgresql: 'pg',
      postgres: 'pg',
      mysql: 'mysql2',
      sqlite: 'better-sqlite3',
    };

    return providerMap[baseDb] || 'pg';
  }

  private generateDrizzleSchema(
    componentName: string,
    component: any,
    provider: string,
    outputDir: string
  ): GeneratedFile {
    const dbType = this.getDbTypeImport(provider);

    let schema = `import { ${dbType} } from 'drizzle-orm/${this.getDriverName(provider)}';\n`;
    schema += `import { `;

    // Collect all column types needed
    const columnTypes = new Set<string>();
    if (component.tables) {
      for (const table of component.tables) {
        if (table.required_fields) {
          for (const field of table.required_fields) {
            columnTypes.add(this.mapTypeToDrizzle(field.type));
          }
        }
        // Add timestamps
        columnTypes.add('timestamp');
      }
    }

    schema += Array.from(columnTypes).join(', ');
    schema += ` } from 'drizzle-orm/${this.getDriverName(provider)}';\n\n`;

    // Generate table definitions
    if (component.tables) {
      for (const table of component.tables) {
        schema += this.generateTable(table, provider);
      }
    }

    return {
      path: path.join(outputDir, componentName, 'src', 'schema.ts'),
      content: schema,
      overwrite: true,
    };
  }

  private generateTable(table: any, provider: string): string {
    const tableName = table.name;
    const varName = this.toCamelCase(table.name);

    let code = `export const ${varName} = ${this.getTableFunction(provider)}('${tableName}', {\n`;

    // Generate fields
    if (table.required_fields) {
      for (const field of table.required_fields) {
        code += this.generateColumn(field);
      }
    }

    // Add timestamps if not explicitly defined
    const hasCreatedAt = table.required_fields?.some((f: any) => f.name === 'createdAt');
    const hasUpdatedAt = table.required_fields?.some((f: any) => f.name === 'updatedAt');

    if (!hasCreatedAt) {
      code += `  createdAt: timestamp('created_at').defaultNow().notNull(),\n`;
    }
    if (!hasUpdatedAt) {
      code += `  updatedAt: timestamp('updated_at').defaultNow().notNull(),\n`;
    }

    code += '});\n\n';

    // Add type export
    const typeName = this.toPascalCase(table.name);
    code += `export type ${typeName} = typeof ${varName}.$inferSelect;\n`;
    code += `export type New${typeName} = typeof ${varName}.$inferInsert;\n\n`;

    return code;
  }

  private generateColumn(field: any): string {
    const columnName = field.name;
    const columnType = this.mapTypeToDrizzle(field.type);
    let line = `  ${columnName}: ${columnType}('${this.toSnakeCase(columnName)}')`;

    // Add modifiers
    if (field.primary_key) {
      line += '.primaryKey()';
    }

    if (field.unique) {
      line += '.unique()';
    }

    if (field.default !== undefined) {
      if (field.type === 'uuid') {
        line += '.defaultRandom()';
      } else if (typeof field.default === 'string') {
        line += `.default('${field.default}')`;
      } else if (typeof field.default === 'boolean' || typeof field.default === 'number') {
        line += `.default(${field.default})`;
      }
    }

    // Add notNull() for required fields
    if (!field.optional) {
      line += '.notNull()';
    }

    line += ',\n';
    return line;
  }

  private mapTypeToDrizzle(type: string): string {
    const typeMap: Record<string, string> = {
      uuid: 'uuid',
      string: 'varchar',
      text: 'text',
      integer: 'integer',
      number: 'integer',
      float: 'real',
      decimal: 'decimal',
      boolean: 'boolean',
      date: 'date',
      datetime: 'timestamp',
      timestamp: 'timestamp',
      json: 'json',
    };

    return typeMap[type.toLowerCase()] || 'varchar';
  }

  private generateDatabaseClient(componentName: string, provider: string, outputDir: string): GeneratedFile {
    let client = '';

    if (provider === 'pg') {
      client = `import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';
import * as schema from './schema';

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
});

export const db = drizzle(pool, { schema });
`;
    } else if (provider === 'mysql2') {
      client = `import { drizzle } from 'drizzle-orm/mysql2';
import mysql from 'mysql2/promise';
import * as schema from './schema';

const connection = await mysql.createConnection({
  uri: process.env.DATABASE_URL,
});

export const db = drizzle(connection, { schema });
`;
    } else if (provider === 'better-sqlite3') {
      client = `import { drizzle } from 'drizzle-orm/better-sqlite3';
import Database from 'better-sqlite3';
import * as schema from './schema';

const sqlite = new Database(process.env.DATABASE_URL || 'sqlite.db');

export const db = drizzle(sqlite, { schema });
`;
    }

    return {
      path: path.join(outputDir, componentName, 'src', 'db.ts'),
      content: client,
      overwrite: true,
    };
  }

  private generateMigrationScript(componentName: string, outputDir: string): GeneratedFile {
    const script = `#!/bin/bash
# Migration script for ${componentName}

echo "Generating migrations..."

# Generate migrations
npx drizzle-kit generate:pg

echo "Running migrations..."

# Run migrations
npx drizzle-kit push:pg

echo "✓ Migrations complete!"
`;

    return {
      path: path.join(outputDir, componentName, 'scripts', 'migrate.sh'),
      content: script,
      overwrite: true,
    };
  }

  private generateDrizzleConfig(componentName: string, provider: string, outputDir: string): GeneratedFile {
    const driverMap: Record<string, string> = {
      pg: 'pg',
      mysql2: 'mysql2',
      'better-sqlite3': 'better-sqlite3',
    };

    const config = {
      schema: './src/schema.ts',
      out: './drizzle',
      driver: driverMap[provider] || 'pg',
      dbCredentials: {
        connectionString: process.env.DATABASE_URL,
      },
    };

    return {
      path: path.join(outputDir, componentName, 'drizzle.config.ts'),
      content: `import type { Config } from 'drizzle-kit';\n\nexport default ${JSON.stringify(config, null, 2)} satisfies Config;\n`,
      overwrite: true,
    };
  }

  private generatePackageJson(componentName: string, provider: string, outputDir: string): GeneratedFile {
    const dependencies: Record<string, string> = {
      'drizzle-orm': '^0.29.0',
    };

    const devDependencies: Record<string, string> = {
      'drizzle-kit': '^0.20.0',
      '@types/node': '^20.0.0',
      typescript: '^5.0.0',
    };

    // Add driver dependencies
    if (provider === 'pg') {
      dependencies['pg'] = '^8.11.0';
      devDependencies['@types/pg'] = '^8.10.0';
    } else if (provider === 'mysql2') {
      dependencies['mysql2'] = '^3.6.0';
    } else if (provider === 'better-sqlite3') {
      dependencies['better-sqlite3'] = '^9.2.0';
      devDependencies['@types/better-sqlite3'] = '^7.6.0';
    }

    const pkg = {
      name: `${componentName}-database`,
      version: '1.0.0',
      type: 'module',
      scripts: {
        'db:generate': 'drizzle-kit generate:pg',
        'db:push': 'drizzle-kit push:pg',
        'db:migrate': './scripts/migrate.sh',
        'db:studio': 'drizzle-kit studio',
      },
      dependencies,
      devDependencies,
    };

    return {
      path: path.join(outputDir, componentName, 'package.json'),
      content: JSON.stringify(pkg, null, 2),
      overwrite: false,
    };
  }

  private generateEnvExample(componentName: string, provider: string, outputDir: string): GeneratedFile {
    const examples: Record<string, string> = {
      pg: 'postgresql://user:password@localhost:5432/mydb',
      mysql2: 'mysql://user:password@localhost:3306/mydb',
      'better-sqlite3': './sqlite.db',
    };

    const exampleUrl = examples[provider] || examples.pg;

    const content = `# Database
DATABASE_URL="${exampleUrl}"
`;

    return {
      path: path.join(outputDir, componentName, '.env.example'),
      content,
      overwrite: false,
    };
  }

  private generateReadme(componentName: string, provider: string, outputDir: string): GeneratedFile {
    const dbName = provider === 'pg' ? 'PostgreSQL' : provider === 'mysql2' ? 'MySQL' : 'SQLite';

    const content = `# ${componentName} Database

Generated with Bangstack using Drizzle ORM.

## Setup

1. Install dependencies:
   \`\`\`bash
   npm install
   \`\`\`

2. Configure your database:
   \`\`\`bash
   cp .env.example .env
   # Edit .env with your database credentials
   \`\`\`

3. Generate and run migrations:
   \`\`\`bash
   npm run db:migrate
   \`\`\`

## Available Scripts

- \`npm run db:generate\` - Generate migrations from schema
- \`npm run db:push\` - Push schema changes directly (no migrations)
- \`npm run db:migrate\` - Run migrations
- \`npm run db:studio\` - Open Drizzle Studio (database GUI)

## Database Provider

This project uses **${dbName}** as the database provider.

## Schema

The Drizzle schema is located at \`src/schema.ts\`.

## Documentation

- [Drizzle Documentation](https://orm.drizzle.team/docs/overview)
- [Drizzle Kit Reference](https://orm.drizzle.team/kit-docs/overview)
`;

    return {
      path: path.join(outputDir, componentName, 'README.md'),
      content,
      overwrite: false,
    };
  }

  // Helper methods
  private getDbTypeImport(provider: string): string {
    const map: Record<string, string> = {
      pg: 'pgTable',
      mysql2: 'mysqlTable',
      'better-sqlite3': 'sqliteTable',
    };
    return map[provider] || 'pgTable';
  }

  private getDriverName(provider: string): string {
    const map: Record<string, string> = {
      pg: 'pg-core',
      mysql2: 'mysql-core',
      'better-sqlite3': 'sqlite-core',
    };
    return map[provider] || 'pg-core';
  }

  private getTableFunction(provider: string): string {
    const map: Record<string, string> = {
      pg: 'pgTable',
      mysql2: 'mysqlTable',
      'better-sqlite3': 'sqliteTable',
    };
    return map[provider] || 'pgTable';
  }

  private toCamelCase(str: string): string {
    return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
  }

  private toPascalCase(str: string): string {
    return str
      .split(/[-_]/)
      .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
      .join('');
  }

  private toSnakeCase(str: string): string {
    return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
  }

  /**
   * Extract database provider from extended workflow
   */
  private extractProviderFromExtended(extendedWorkflow: any): string {
    const dbType = extendedWorkflow.technology?.database?.type?.toLowerCase();

    const providerMap: Record<string, string> = {
      postgresql: 'pg',
      postgres: 'pg',
      mysql: 'mysql2',
      sqlite: 'better-sqlite3',
    };

    return providerMap[dbType || 'postgresql'] || 'pg';
  }

  /**
   * Generate Drizzle schema from extended workflow models
   */
  private generateDrizzleSchemaFromModels(
    models: Record<string, any>,
    provider: string,
    outputDir: string,
    componentName: string
  ): GeneratedFile {
    const dbType = this.getDbTypeImport(provider);
    const driverName = this.getDriverName(provider);

    let schema = `import { ${dbType} } from 'drizzle-orm/${driverName}';\n`;
    schema += `import { `;

    // Collect all column types needed
    const columnTypes = new Set<string>();
    for (const [_, model] of Object.entries(models)) {
      if (model.fields) {
        for (const [__, field] of Object.entries(model.fields as Record<string, any>)) {
          const drizzleType = this.mapTypeToDrizzle(field.type);
          columnTypes.add(drizzleType);

          // Add enum type if needed
          if (field.type === 'enum') {
            columnTypes.add('pgEnum');
          }
        }
      }
    }

    schema += Array.from(columnTypes).join(', ');
    schema += `, serial, relations } from 'drizzle-orm/${driverName}';\n\n`;

    // Generate enum definitions first
    for (const [modelName, model] of Object.entries(models)) {
      if (model.fields) {
        for (const [fieldName, field] of Object.entries(model.fields as Record<string, any>)) {
          if (field.type === 'enum' && field.enum_values) {
            const enumName = `${modelName.toLowerCase()}${this.toPascalCase(fieldName)}Enum`;
            schema += `export const ${enumName} = pgEnum('${this.toSnakeCase(modelName)}_${this.toSnakeCase(fieldName)}', [`;
            schema += field.enum_values.map((v: string) => `'${v}'`).join(', ');
            schema += ']);\n\n';
          }
        }
      }
    }

    // Generate table definitions
    for (const [modelName, model] of Object.entries(models)) {
      schema += this.generateTableFromModel(modelName, model, provider);
    }

    // Generate relations
    schema += this.generateRelations(models);

    return {
      path: path.join(outputDir, componentName, 'src', 'schema.ts'),
      content: schema,
      overwrite: true,
    };
  }

  /**
   * Generate a Drizzle table from an extended workflow model
   */
  private generateTableFromModel(modelName: string, model: any, provider: string): string {
    const tableName = this.toSnakeCase(modelName);
    const varName = this.toCamelCase(modelName.toLowerCase());

    let code = `export const ${varName} = ${this.getTableFunction(provider)}('${tableName}', {\n`;

    // Generate fields
    if (model.fields) {
      for (const [fieldName, field] of Object.entries(model.fields as Record<string, any>)) {
        code += this.generateColumnFromField(fieldName, field, modelName);
      }
    }

    code += '});\n\n';

    // Add type exports
    code += `export type ${modelName} = typeof ${varName}.$inferSelect;\n`;
    code += `export type New${modelName} = typeof ${varName}.$inferInsert;\n\n`;

    return code;
  }

  /**
   * Generate a Drizzle column from an extended workflow field
   */
  private generateColumnFromField(fieldName: string, field: any, modelName: string): string {
    const columnName = this.toSnakeCase(fieldName);
    const columnType = this.mapTypeToDrizzle(field.type);

    // Handle enum types specially
    if (field.type === 'enum' && field.enum_values) {
      const enumName = `${modelName.toLowerCase()}${this.toPascalCase(fieldName)}Enum`;
      let line = `  ${fieldName}: ${enumName}('${columnName}')`;

      if (field.default) {
        line += `.default('${field.default}')`;
      }

      if (field.required) {
        line += '.notNull()';
      }

      line += ',\n';
      return line;
    }

    let line = `  ${fieldName}: ${columnType}('${columnName}')`;

    // Add modifiers based on field properties
    if (field.type === 'uuid' && field.system_field) {
      line += '.defaultRandom().primaryKey()';
    }

    if (field.unique) {
      line += '.unique()';
    }

    if (field.default !== undefined && field.type !== 'uuid') {
      if (typeof field.default === 'string') {
        line += `.default('${field.default}')`;
      } else if (typeof field.default === 'boolean' || typeof field.default === 'number') {
        line += `.default(${field.default})`;
      }
    }

    // Handle timestamps with defaultNow
    if ((field.type === 'timestamp' || field.type === 'datetime') && field.system_field) {
      line += '.defaultNow()';
    }

    // Add notNull() for required fields
    if (field.required) {
      line += '.notNull()';
    }

    line += ',\n';
    return line;
  }

  /**
   * Generate Drizzle relations from models
   */
  private generateRelations(models: Record<string, any>): string {
    let relationsCode = '';

    for (const [modelName, model] of Object.entries(models)) {
      const varName = this.toCamelCase(modelName.toLowerCase());
      const hasRelations = model.fields && Object.values(model.fields as Record<string, any>).some(
        (field: any) => field.foreign_key
      );

      if (hasRelations) {
        relationsCode += `export const ${varName}Relations = relations(${varName}, ({ one, many }) => ({\n`;

        for (const [fieldName, field] of Object.entries(model.fields as Record<string, any>)) {
          if (field.foreign_key) {
            const targetModel = field.foreign_key;
            const targetVarName = this.toCamelCase(targetModel.toLowerCase());
            const relationName = fieldName.replace(/_id$/, '');

            relationsCode += `  ${relationName}: one(${targetVarName}, {\n`;
            relationsCode += `    fields: [${varName}.${fieldName}],\n`;
            relationsCode += `    references: [${targetVarName}.id],\n`;
            relationsCode += `  }),\n`;
          }
        }

        relationsCode += '}));\n\n';
      }
    }

    return relationsCode;
  }
}
