/**
 * Prisma Database Generator
 * Generates Prisma schema and migration files from workflow definitions
 */

import { DatabaseGenerator, GeneratorContext, GenerationResult, GeneratedFile } from '../types';
import * as path from 'path';
import { SeedDataGenerator, TableSchema } from './seed-data-generator';
import { PrismaSchemaBuilder } from './prisma-schema-builder';

export class PrismaGenerator implements DatabaseGenerator {
  id = 'prisma-database';
  name = 'Prisma Database Generator';
  description = 'Generates Prisma schema and migrations';
  database = 'prisma';

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

    // Use extended workflow if available, otherwise fall back to legacy
    if (extendedWorkflow) {
      return this.generateFromExtendedWorkflow(extendedWorkflow, outputDirectory);
    }

    // Legacy generation from stack.tables
    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 Prisma schema
      files.push(this.generatePrismaSchema(componentName, component, provider, outputDirectory));

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

      // Generate seed script with table data
      files.push(this.generateSeedScript(componentName, outputDirectory, component.tables));

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

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

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

    return { files };
  }

  /**
   * Generate from extended workflow schema with models and relationships
   */
  private generateFromExtendedWorkflow(workflow: any, outputDir: string): GenerationResult {
    const files: GeneratedFile[] = [];

    if (!workflow.models || Object.keys(workflow.models).length === 0) {
      return { files: [], warnings: ['No models defined in workflow'] };
    }

    // Determine database provider from technology section
    const provider = workflow.technology?.database?.type || 'postgresql';
    const componentName = 'database'; // Default component name

    // Generate Prisma schema from models
    files.push(this.generatePrismaSchemaFromModels(workflow, provider, outputDir));

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

    // Generate seed script (no tables for extended workflow yet)
    files.push(this.generateSeedScript(componentName, outputDir));

    // Generate package.json
    files.push(this.generatePackageJson(componentName, outputDir));

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

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

    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 {
    // Support both "postgresql/prisma" and "postgresql" (default to Prisma)
    return (
      technology.includes('prisma') ||
      technology === 'postgresql' ||
      technology === 'mysql' ||
      technology === 'sqlite' ||
      technology === 'mongodb' ||
      technology === 'sqlserver'
    );
  }

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

    // Map to Prisma provider names
    const providerMap: Record<string, string> = {
      postgresql: 'postgresql',
      postgres: 'postgresql',
      mysql: 'mysql',
      sqlite: 'sqlite',
      mongodb: 'mongodb',
      sqlserver: 'sqlserver',
    };

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

  private generatePrismaSchema(
    componentName: string,
    component: any,
    provider: string,
    outputDir: string
  ): GeneratedFile {
    let schema = `// Prisma Schema
// Generated from workflow definition

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "${provider}"
  url      = env("DATABASE_URL")
}

`;

    // Generate models from tables
    if (component.tables) {
      for (const table of component.tables) {
        schema += this.generateModel(table);
      }
    }

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

  private generateModel(table: any): string {
    const modelName = this.toPascalCase(table.name);
    let model = `model ${modelName} {\n`;
    const indexedFields: string[] = [];

    // Generate fields
    if (table.required_fields) {
      for (const field of table.required_fields) {
        model += this.generateField(field);
        // Collect indexed fields for model-level @@index
        if (field.indexed) {
          indexedFields.push(field.name);
        }
      }
    }

    // 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) {
      model += '  createdAt DateTime @default(now())\n';
    }
    if (!hasUpdatedAt) {
      model += '  updatedAt DateTime @updatedAt\n';
    }

    // Add model-level indexes for fields marked with indexed: true
    if (indexedFields.length > 0) {
      model += '\n';
      for (const fieldName of indexedFields) {
        model += `  @@index([${fieldName}])\n`;
      }
    }

    model += '}\n\n';
    return model;
  }

  private generateField(field: any): string {
    const fieldName = field.name;
    const fieldType = this.mapTypeToPrisma(field.type);
    let line = `  ${fieldName} ${fieldType}`;

    // Add modifiers
    const modifiers: string[] = [];

    if (field.primary_key) {
      modifiers.push('@id');
      if (field.type === 'uuid') {
        modifiers.push('@default(uuid())');
      } else if (field.type === 'integer' || field.type === 'number') {
        modifiers.push('@default(autoincrement())');
      }
    }

    if (field.unique) {
      modifiers.push('@unique');
    }

    // Note: indexed fields are handled at model level with @@index in generateModel()
    // Prisma doesn't support field-level @index, only model-level @@index

    if (field.default !== undefined) {
      if (typeof field.default === 'string') {
        modifiers.push(`@default("${field.default}")`);
      } else if (typeof field.default === 'boolean') {
        modifiers.push(`@default(${field.default})`);
      } else if (typeof field.default === 'number') {
        modifiers.push(`@default(${field.default})`);
      }
    }

    if (field.foreign_key) {
      // Parse foreign_key: "users.id"
      const [refTable, refField] = field.foreign_key.split('.');
      const _refModel = this.toPascalCase(refTable);

      // Add relation field
      modifiers.push(`@relation(fields: [${fieldName}], references: [${refField}])`);

      // Add relation on next line
      const relationName = this.toPascalCase(refTable);
      line += ` ${modifiers.join(' ')}\n  ${relationName.toLowerCase()} ${relationName}`;
      return line + '\n';
    }

    if (modifiers.length > 0) {
      line += ` ${modifiers.join(' ')}`;
    }

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

  private mapTypeToPrisma(type: string): string {
    const typeMap: Record<string, string> = {
      uuid: 'String',
      string: 'String',
      text: 'String',
      integer: 'Int',
      number: 'Int',
      float: 'Float',
      decimal: 'Decimal',
      boolean: 'Boolean',
      date: 'DateTime',
      datetime: 'DateTime',
      timestamp: 'DateTime',
      json: 'Json',
    };

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

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

echo "Running Prisma migrations..."

# Generate Prisma Client
npx prisma generate

# Run migrations
npx prisma migrate dev --name init

echo "✓ Migrations complete!"
`;

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

  private generateSeedScript(componentName: string, outputDir: string, tables?: any[]): GeneratedFile {
    let script: string;

    // If we have table schemas, generate smart seed data
    if (tables && tables.length > 0) {
      const tableSchemas = this.convertToTableSchemas(tables);
      const seedGenerator = new SeedDataGenerator({ recordsPerTable: 5 });
      script = seedGenerator.generateSeedData(tableSchemas);
    } else {
      // Fallback to empty template
      script = `import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

async function main() {
  console.log('🌱 Seeding database...');

  // Add your seed data here
  // Example:
  // await prisma.user.create({
  //   data: {
  //     email: 'test@example.com',
  //     name: 'Test User',
  //   },
  // });

  console.log('✅ Seeding complete!');
}

main()
  .catch((e) => {
    console.error('❌ Seeding failed:', e);
    process.exit(1);
  })
  .finally(async () => {
    await prisma.$disconnect();
  });
`;
    }

    return {
      path: path.join(outputDir, componentName, 'prisma', 'seed.ts'),
      content: script,
      overwrite: true,
    };
  }

  /**
   * Convert workflow table definitions to TableSchema format
   */
  private convertToTableSchemas(tables: any[]): TableSchema[] {
    return tables.map(table => ({
      name: table.name,
      fields: (table.required_fields || []).map((field: any) => ({
        name: field.name,
        type: field.type,
        primaryKey: field.primary_key,
        unique: field.unique,
        foreignKey: field.foreign_key,
        default: field.default,
        optional: false,
      })),
    }));
  }

  private generatePackageJson(componentName: string, outputDir: string): GeneratedFile {
    const pkg = {
      name: `${componentName}-database`,
      version: '1.0.0',
      scripts: {
        'db:generate': 'prisma generate',
        'db:migrate': 'prisma migrate dev',
        'db:push': 'prisma db push',
        'db:seed': 'ts-node prisma/seed.ts',
        'db:studio': 'prisma studio',
      },
      dependencies: {
        '@prisma/client': '^5.0.0',
      },
      devDependencies: {
        prisma: '^5.0.0',
        'ts-node': '^10.9.0',
        '@types/node': '^20.0.0',
        typescript: '^5.0.0',
      },
      prisma: {
        seed: 'ts-node prisma/seed.ts',
      },
    };

    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> = {
      postgresql: 'postgresql://user:password@localhost:5432/mydb',
      mysql: 'mysql://user:password@localhost:3306/mydb',
      sqlite: 'file:./dev.db',
      mongodb: 'mongodb://localhost:27017/mydb',
      sqlserver: 'sqlserver://localhost:1433;database=mydb;user=sa;password=password',
    };

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

    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 content = `# ${componentName} Database

Generated with Bangstack using Prisma ORM.

## Setup

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

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

3. Run migrations:
   \`\`\`bash
   npm run db:migrate
   \`\`\`

4. (Optional) Seed the database:
   \`\`\`bash
   npm run db:seed
   \`\`\`

## Available Scripts

- \`npm run db:generate\` - Generate Prisma Client
- \`npm run db:migrate\` - Run database migrations
- \`npm run db:push\` - Push schema changes without migrations
- \`npm run db:seed\` - Seed the database
- \`npm run db:studio\` - Open Prisma Studio (database GUI)

## Database Provider

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

## Schema

The Prisma schema is located at \`prisma/schema.prisma\`.

## Documentation

- [Prisma Documentation](https://www.prisma.io/docs)
- [Prisma CLI Reference](https://www.prisma.io/docs/reference/api-reference/command-reference)
`;

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

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

  /**
   * Generate Prisma schema from extended workflow models using the reusable builder
   */
  private generatePrismaSchemaFromModels(workflow: any, provider: string, outputDir: string): GeneratedFile {
    const schema = PrismaSchemaBuilder.generateSchema(workflow, {
      provider,
      includeGenerator: true,
      includeDatasource: true,
    });

    return {
      path: path.join(outputDir, 'database', 'prisma', 'schema.prisma'),
      content: schema,
      overwrite: true,
    };
  }

}
