/**
 * Drizzle Schema Builder Utility
 * Reusable utility for generating Drizzle ORM schemas from workflow models
 * Can be used by any generator (backend, database, etc.)
 */

export interface DrizzleSchemaOptions {
  provider?: 'postgresql' | 'mysql' | 'sqlite';
  outputPath?: string;
}

export class DrizzleSchemaBuilder {
  /**
   * Generate a complete Drizzle schema from extended workflow models
   */
  static generateSchema(workflow: any, options: DrizzleSchemaOptions = {}): string {
    const { provider = 'postgresql' } = options;

    let schema = `// Drizzle Schema\n// Generated from Bangstack workflow\n\n`;

    // Import statements based on provider
    schema += this.generateImports(provider, workflow);
    schema += '\n';

    // Generate table definitions
    if (!workflow.models || Object.keys(workflow.models).length === 0) {
      return schema;
    }

    for (const [modelName, modelDef] of Object.entries(workflow.models as Record<string, any>)) {
      schema += this.generateTable(modelName, modelDef, workflow, provider);
      schema += '\n';
    }

    // Generate relations
    if (workflow.relationships && workflow.relationships.length > 0) {
      schema += this.generateRelations(workflow);
    }

    return schema;
  }

  /**
   * Generate import statements based on provider and models
   */
  private static generateImports(provider: string, workflow: any): string {
    const imports = new Set<string>();

    // Base imports
    if (provider === 'postgresql') {
      imports.add('pgTable');
    } else if (provider === 'mysql') {
      imports.add('mysqlTable');
    } else if (provider === 'sqlite') {
      imports.add('sqliteTable');
    }

    // Field type imports based on what's used in the models
    const fieldTypes = new Set<string>();
    for (const [_, modelDef] of Object.entries(workflow.models as Record<string, any>)) {
      for (const [_, fieldDef] of Object.entries(modelDef.fields as Record<string, any>)) {
        const drizzleType = this.getDrizzleFieldType(fieldDef.type);
        fieldTypes.add(drizzleType);
      }

      // Always add timestamp types if timestamps enabled
      if (modelDef.timestamps !== false) {
        fieldTypes.add('timestamp');
      }

      // Add timestamp for soft delete
      if (modelDef.soft_delete) {
        fieldTypes.add('timestamp');
      }
    }

    const providerModule = provider === 'postgresql' ? 'drizzle-orm/pg-core' :
                          provider === 'mysql' ? 'drizzle-orm/mysql-core' :
                          'drizzle-orm/sqlite-core';

    let importStatement = `import { ${Array.from(fieldTypes).sort().join(', ')}`;
    if (provider === 'postgresql') {
      importStatement += ', pgTable';
    } else if (provider === 'mysql') {
      importStatement += ', mysqlTable';
    } else {
      importStatement += ', sqliteTable';
    }
    importStatement += ` } from '${providerModule}';\n`;

    // Add relations import if there are relationships
    if (workflow.relationships && workflow.relationships.length > 0) {
      importStatement += `import { relations } from 'drizzle-orm';\n`;
    }

    return importStatement;
  }

  /**
   * Generate a table definition from workflow model
   */
  private static generateTable(
    modelName: string,
    modelDef: any,
    workflow: any,
    provider: string
  ): string {
    const tableName = this.toSnakeCase(modelName);
    const tableFunc = provider === 'postgresql' ? 'pgTable' :
                     provider === 'mysql' ? 'mysqlTable' :
                     'sqliteTable';

    let table = `export const ${this.toCamelCase(modelName)} = ${tableFunc}('${tableName}', {\n`;

    // Auto-generate ID field if not present
    const hasId = Object.entries(modelDef.fields).some(([name, _]: [string, any]) =>
      name === 'id'
    );

    if (!hasId) {
      table += `  id: ${this.getIdField(provider)},\n`;
    }

    // Collect foreign key fields to handle them separately
    const foreignKeyFields: Map<string, any> = new Map();
    for (const [fieldName, fieldDef] of Object.entries(modelDef.fields as Record<string, any>)) {
      if (fieldDef.foreign_key) {
        foreignKeyFields.set(fieldName, fieldDef);
      }
    }

    // Generate regular fields
    for (const [fieldName, fieldDef] of Object.entries(modelDef.fields as Record<string, any>)) {
      // Skip system timestamp fields - we'll add them later
      if (fieldDef.system_field && (fieldName === 'created_at' || fieldName === 'updated_at')) {
        continue;
      }

      // Skip foreign key fields - they're handled separately
      if (fieldDef.foreign_key) {
        continue;
      }

      table += this.generateField(fieldName, fieldDef, provider);
    }

    // Generate foreign key fields
    for (const [fkFieldName, fkFieldDef] of foreignKeyFields) {
      table += this.generateForeignKeyField(fkFieldName, fkFieldDef, workflow, provider);
    }

    // Add timestamps if enabled (default: true)
    if (modelDef.timestamps !== false) {
      const hasCreatedAt = Object.keys(modelDef.fields).includes('created_at');
      const hasUpdatedAt = Object.keys(modelDef.fields).includes('updated_at');

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

    // Add soft delete field if enabled
    if (modelDef.soft_delete) {
      table += `  deletedAt: timestamp('deleted_at'),\n`;
    }

    table += `});\n`;

    return table;
  }

  /**
   * Generate a field definition
   */
  private static generateField(fieldName: string, fieldDef: any, _provider: string): string {
    const drizzleType = this.getDrizzleFieldType(fieldDef.type);
    const dbColumnName = this.toSnakeCase(fieldName);
    const camelFieldName = this.toCamelCase(fieldName);

    let field = `  ${camelFieldName}: ${drizzleType}('${dbColumnName}')`;

    // Add max length for string/text fields
    if ((fieldDef.type === 'string' || fieldDef.type === 'email') && fieldDef.max_length) {
      field = `  ${camelFieldName}: ${drizzleType}('${dbColumnName}', { length: ${fieldDef.max_length} })`;
    }

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

    // Primary key
    if (fieldDef.primary_key || fieldName === 'id') {
      modifiers.push('primaryKey()');
    }

    // Not null (required)
    if (fieldDef.required) {
      modifiers.push('notNull()');
    }

    // Unique
    if (fieldDef.unique) {
      modifiers.push('unique()');
    }

    // Default values
    if (fieldDef.default !== undefined && !fieldDef.primary_key) {
      if (fieldDef.type === 'timestamp' || fieldDef.type === 'date' || fieldDef.type === 'datetime') {
        modifiers.push('defaultNow()');
      } else if (typeof fieldDef.default === 'string') {
        modifiers.push(`default('${fieldDef.default}')`);
      } else if (typeof fieldDef.default === 'boolean' || typeof fieldDef.default === 'number') {
        modifiers.push(`default(${fieldDef.default})`);
      }
    }

    if (modifiers.length > 0) {
      field += '.' + modifiers.join('.');
    }

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

  /**
   * Generate a foreign key field
   */
  private static generateForeignKeyField(
    fkFieldName: string,
    fkFieldDef: any,
    _workflow: any,
    _provider: string
  ): string {
    const drizzleType = this.getDrizzleFieldType(fkFieldDef.type);
    const dbColumnName = this.toSnakeCase(fkFieldName);
    const camelFieldName = this.toCamelCase(fkFieldName);
    const relatedModel = fkFieldDef.foreign_key;
    const relatedTable = this.toCamelCase(relatedModel);

    let field = `  ${camelFieldName}: ${drizzleType}('${dbColumnName}')`;

    const modifiers: string[] = [];

    if (fkFieldDef.required) {
      modifiers.push('notNull()');
    }

    modifiers.push(`references(() => ${relatedTable}.id)`);

    if (modifiers.length > 0) {
      field += '.' + modifiers.join('.');
    }

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

  /**
   * Generate relations definitions
   */
  private static generateRelations(workflow: any): string {
    let relations = `// Relations\n`;

    const modelRelations: Map<string, string[]> = new Map();

    // Build relations map
    for (const rel of workflow.relationships) {
      const fromModel = rel.from;
      const toModel = rel.to;

      // Forward relation (one-to-many)
      if (!modelRelations.has(fromModel)) {
        modelRelations.set(fromModel, []);
      }
      const toFieldName = this.toCamelCase(toModel) + 's';
      modelRelations.get(fromModel)!.push(
        `    ${toFieldName}: many(${this.toCamelCase(toModel)})`
      );

      // Inverse relation (many-to-one)
      if (rel.inverse !== false) {
        if (!modelRelations.has(toModel)) {
          modelRelations.set(toModel, []);
        }

        // Find the actual foreign key field name in the toModel that references fromModel
        const toModelDef = workflow.models[toModel];
        let fkFieldName = null;

        if (toModelDef && toModelDef.fields) {
          for (const [fieldName, fieldDef] of Object.entries(toModelDef.fields as Record<string, any>)) {
            if (fieldDef.foreign_key === fromModel) {
              fkFieldName = this.toCamelCase(fieldName);
              break;
            }
          }
        }

        // If no FK field found, skip this inverse relation
        if (!fkFieldName) {
          continue;
        }

        const fromFieldName = this.toCamelCase(fromModel);
        modelRelations.get(toModel)!.push(
          `    ${fromFieldName}: one(${this.toCamelCase(fromModel)}, {\n` +
          `      fields: [${this.toCamelCase(toModel)}.${fkFieldName}],\n` +
          `      references: [${this.toCamelCase(fromModel)}.id]\n` +
          `    })`
        );
      }
    }

    // Generate relation definitions
    for (const [modelName, rels] of modelRelations) {
      relations += `\nexport const ${this.toCamelCase(modelName)}Relations = relations(${this.toCamelCase(modelName)}, ({ one, many }) => ({\n`;
      relations += rels.join(',\n');
      relations += '\n}));\n';
    }

    return relations;
  }

  /**
   * Get ID field definition based on provider
   */
  private static getIdField(provider: string): string {
    if (provider === 'postgresql') {
      return `uuid('id').primaryKey().defaultRandom()`;
    } else if (provider === 'mysql') {
      return `varchar('id', { length: 36 }).primaryKey().$defaultFn(() => crypto.randomUUID())`;
    } else {
      return `text('id').primaryKey().$defaultFn(() => crypto.randomUUID())`;
    }
  }

  /**
   * Map workflow field types to Drizzle field types
   */
  private static getDrizzleFieldType(type: string): string {
    const typeMap: Record<string, string> = {
      uuid: 'uuid',
      string: 'varchar',
      text: 'text',
      email: 'varchar',
      password: 'varchar',
      integer: 'integer',
      number: 'integer',
      float: 'real',
      decimal: 'decimal',
      boolean: 'boolean',
      date: 'date',
      datetime: 'timestamp',
      timestamp: 'timestamp',
      json: 'json',
      enum: 'varchar',
    };

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

  private static toCamelCase(str: string): string {
    return str.charAt(0).toLowerCase() + str.slice(1);
  }

  private static toSnakeCase(str: string): string {
    return str
      .replace(/([A-Z])/g, '_$1')
      .toLowerCase()
      .replace(/^_/, '');
  }
}
