#!/usr/bin/env ts-node
/**
 * Migration Script: Convert Old Media URLs to MediaReferences
 * 
 * This script migrates existing seeded data from old stored route strings/URLs 
 * to the new MediaReference format. It handles:
 * 
 * - Driver avatars: /api/avatar/{id} -> system-default (deterministic variant)
 * - Team logos: /api/media/teams/{id}/logo -> generated
 * - League logos: /api/media/leagues/{id}/logo -> generated
 * - Other old formats -> none
 * 
 * Usage:
 *   # Test mode (dry run, no changes)
 *   npm run migrate:media:test
 *   
 *   # Execute migration
 *   npm run migrate:media:exec
 * 
 * Environment:
 *   - GRIDPILOT_API_PERSISTENCE=postgres|inmemory (default: postgres)
 *   - DATABASE_URL (for postgres mode)
 */

import { DataSource } from 'typeorm';
import { Logger } from '@core/shared/application';
import { MediaReference } from '@core/domain/media/MediaReference';

// Import entities
import { DriverOrmEntity } from '../adapters/racing/persistence/typeorm/entities/DriverOrmEntity';
import { TeamOrmEntity } from '../adapters/racing/persistence/typeorm/entities/TeamOrmEntities';
import { LeagueOrmEntity } from '../adapters/racing/persistence/typeorm/entities/LeagueOrmEntity';

// Import in-memory repositories for testing
import { InMemoryDriverRepository } from '../adapters/racing/persistence/inmemory/InMemoryDriverRepository';
import { InMemoryTeamRepository } from '../adapters/racing/persistence/inmemory/InMemoryTeamRepository';
import { InMemoryLeagueRepository } from '../adapters/racing/persistence/inmemory/InMemoryLeagueRepository';
import { InMemoryMediaRepository } from '../adapters/racing/persistence/media/InMemoryMediaRepository';

interface MigrationResult {
  success: boolean;
  processed: {
    drivers: number;
    teams: number;
    leagues: number;
  };
  updated: {
    drivers: number;
    teams: number;
    leagues: number;
  };
  errors: string[];
}

class MediaMigrationLogger implements Logger {
  private getTimestamp(): string {
    return new Date().toISOString();
  }

  info(message: string): void {
    console.log(`[${this.getTimestamp()}] [INFO] ${message}`);
  }

  warn(message: string): void {
    console.warn(`[${this.getTimestamp()}] [WARN] ${message}`);
  }

  error(message: string, trace?: string): void {
    console.error(`[${this.getTimestamp()}] [ERROR] ${message}`, trace || '');
  }

  debug(message: string): void {
    console.debug(`[${this.getTimestamp()}] [DEBUG] ${message}`);
  }
}

export class MediaReferenceMigration {
  private logger: Logger;

  constructor(private readonly dryRun: boolean = true) {
    this.logger = new MediaMigrationLogger();
  }

  /**
   * Parse old URL format and determine appropriate MediaReference
   */
  private parseOldUrl(url: string | null | undefined): MediaReference | null {
    if (!url || typeof url !== 'string') {
      return null;
    }

    const trimmed = url.trim();
    if (trimmed === '') {
      return null;
    }

    // Pattern: /api/avatar/{driverId}
    const avatarMatch = trimmed.match(/^\/api\/avatar\/([a-zA-Z0-9-]+)$/);
    if (avatarMatch) {
      const driverId = avatarMatch[1];
      const variant = this.getDeterministicAvatarVariant(driverId);
      return MediaReference.systemDefault(variant);
    }

    // Pattern: /api/media/teams/{teamId}/logo
    const teamLogoMatch = trimmed.match(/^\/api\/media\/teams\/([a-zA-Z0-9-]+)\/logo$/);
    if (teamLogoMatch) {
      const teamId = teamLogoMatch[1];
      return MediaReference.generated('team', teamId);
    }

    // Pattern: /api/media/leagues/{leagueId}/logo
    const leagueLogoMatch = trimmed.match(/^\/api\/media\/leagues\/([a-zA-Z0-9-]+)\/logo$/);
    if (leagueLogoMatch) {
      const leagueId = leagueLogoMatch[1];
      return MediaReference.generated('league', leagueId);
    }

    // Pattern: /api/media/teams/{teamId}/logo (alternative format)
    const teamLogoAltMatch = trimmed.match(/^\/api\/teams\/([a-zA-Z0-9-]+)\/logo$/);
    if (teamLogoAltMatch) {
      const teamId = teamLogoAltMatch[1];
      return MediaReference.generated('team', teamId);
    }

    // Pattern: Static file paths (old format)
    if (trimmed.includes('/images/avatars/')) {
      // Old static files - convert to system default
      if (trimmed.includes('male')) return MediaReference.systemDefault('male');
      if (trimmed.includes('female')) return MediaReference.systemDefault('female');
      if (trimmed.includes('neutral')) return MediaReference.systemDefault('neutral');
      return MediaReference.systemDefault('avatar');
    }

    // Pattern: Full URLs (external or old API)
    if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {
      // External URLs - can't migrate automatically, mark as none
      this.logger.warn(`External URL found: ${trimmed} - converting to none`);
      return MediaReference.createNone();
    }

    // Unknown format - convert to none
    this.logger.warn(`Unknown URL format: ${trimmed} - converting to none`);
    return MediaReference.createNone();
  }

  /**
   * Deterministic avatar variant selection based on driver ID
   * Uses hash % 3 to ensure consistency
   */
  private getDeterministicAvatarVariant(driverId: string): 'male' | 'female' | 'neutral' {
    const hash = this.hashCode(driverId);
    const variantIndex = Math.abs(hash) % 3;
    
    switch (variantIndex) {
      case 0: return 'male';
      case 1: return 'female';
      case 2: return 'neutral';
      default: return 'neutral';
    }
  }

  /**
   * Simple hash function for deterministic selection
   */
  private hashCode(str: string): number {
    let hash = 0;
    for (let i = 0; i < str.length; i++) {
      const char = str.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash;
    }
    return hash;
  }

  /**
   * Migrate PostgreSQL database
   */
  async migratePostgres(): Promise<MigrationResult> {
    const result: MigrationResult = {
      success: false,
      processed: { drivers: 0, teams: 0, leagues: 0 },
      updated: { drivers: 0, teams: 0, leagues: 0 },
      errors: []
    };

    try {
      const databaseUrl = process.env.DATABASE_URL;
      if (!databaseUrl) {
        throw new Error('DATABASE_URL environment variable is required for postgres migration');
      }

      this.logger.info('Connecting to PostgreSQL database...');

      const dataSource = new DataSource({
        type: 'postgres',
        url: databaseUrl,
        entities: [DriverOrmEntity, TeamOrmEntity, LeagueOrmEntity],
        synchronize: false, // Don't auto-create tables
        logging: false
      });

      await dataSource.initialize();
      this.logger.info('Database connection established');

      // Migrate Drivers
      const driverRepo = dataSource.getRepository(DriverOrmEntity);
      const drivers = await driverRepo.find();
      result.processed.drivers = drivers.length;
      this.logger.info(`Found ${drivers.length} drivers to migrate`);

      for (const driver of drivers) {
        try {
          // Check if avatarRef already exists and is valid
          if (driver.avatarRef && typeof driver.avatarRef === 'object') {
            try {
              const existing = MediaReference.fromJSON(driver.avatarRef as any);
              // Valid reference, skip
              continue;
            } catch {
              // Invalid reference, proceed with migration
            }
          }

          // Get old URL from avatarRef if it's a string, or check if we need to parse
          let oldUrl: string | null = null;
          
          if (driver.avatarRef && typeof driver.avatarRef === 'string') {
            oldUrl = driver.avatarRef;
          } else if (driver.avatarRef && typeof driver.avatarRef === 'object') {
            // Try to extract URL from old object format
            const refAny = driver.avatarRef as any;
            oldUrl = refAny.url || refAny.avatarUrl || null;
          }

          if (oldUrl) {
            const newRef = this.parseOldUrl(oldUrl);
            if (newRef) {
              if (!this.dryRun) {
                await driverRepo.update(driver.id, {
                  avatarRef: newRef.toJSON() as any
                });
                result.updated.drivers++;
              } else {
                result.updated.drivers++; // Count as would-be update
              }
            }
          }
        } catch (error) {
          result.errors.push(`Driver ${driver.id}: ${error instanceof Error ? error.message : String(error)}`);
        }
      }

      // Migrate Teams
      const teamRepo = dataSource.getRepository(TeamOrmEntity);
      const teams = await teamRepo.find();
      result.processed.teams = teams.length;
      this.logger.info(`Found ${teams.length} teams to migrate`);

      for (const team of teams) {
        try {
          // Check if logoRef already exists and is valid
          if (team.logoRef && typeof team.logoRef === 'object') {
            try {
              const existing = MediaReference.fromJSON(team.logoRef as any);
              // Valid reference, skip
              continue;
            } catch {
              // Invalid reference, proceed with migration
            }
          }

          let oldUrl: string | null = null;
          
          if (team.logoRef && typeof team.logoRef === 'string') {
            oldUrl = team.logoRef;
          } else if (team.logoRef && typeof team.logoRef === 'object') {
            const refAny = team.logoRef as any;
            oldUrl = refAny.url || refAny.logoUrl || null;
          }

          if (oldUrl) {
            const newRef = this.parseOldUrl(oldUrl);
            if (newRef) {
              if (!this.dryRun) {
                await teamRepo.update(team.id, {
                  logoRef: newRef.toJSON() as any
                });
                result.updated.teams++;
              } else {
                result.updated.teams++;
              }
            }
          }
        } catch (error) {
          result.errors.push(`Team ${team.id}: ${error instanceof Error ? error.message : String(error)}`);
        }
      }

      // Migrate Leagues
      const leagueRepo = dataSource.getRepository(LeagueOrmEntity);
      const leagues = await leagueRepo.find();
      result.processed.leagues = leagues.length;
      this.logger.info(`Found ${leagues.length} leagues to migrate`);

      for (const league of leagues) {
        try {
          // Check if logoRef already exists and is valid
          if (league.logoRef && typeof league.logoRef === 'object') {
            try {
              const existing = MediaReference.fromJSON(league.logoRef as any);
              // Valid reference, skip
              continue;
            } catch {
              // Invalid reference, proceed with migration
            }
          }

          let oldUrl: string | null = null;
          
          if (league.logoRef && typeof league.logoRef === 'string') {
            oldUrl = league.logoRef;
          } else if (league.logoRef && typeof league.logoRef === 'object') {
            const refAny = league.logoRef as any;
            oldUrl = refAny.url || refAny.logoUrl || null;
          }

          if (oldUrl) {
            const newRef = this.parseOldUrl(oldUrl);
            if (newRef) {
              if (!this.dryRun) {
                await leagueRepo.update(league.id, {
                  logoRef: newRef.toJSON() as any
                });
                result.updated.leagues++;
              } else {
                result.updated.leagues++;
              }
            }
          }
        } catch (error) {
          result.errors.push(`League ${league.id}: ${error instanceof Error ? error.message : String(error)}`);
        }
      }

      await dataSource.destroy();
      result.success = true;

      this.logger.info(`Migration completed: ${result.updated.drivers} drivers, ${result.updated.teams} teams, ${result.updated.leagues} leagues updated`);

      return result;

    } catch (error) {
      result.errors.push(error instanceof Error ? error.message : String(error));
      this.logger.error('Migration failed', error instanceof Error ? error.stack : undefined);
      return result;
    }
  }

  /**
   * Migrate in-memory repositories (for testing)
   */
  async migrateInMemory(): Promise<MigrationResult> {
    const result: MigrationResult = {
      success: false,
      processed: { drivers: 0, teams: 0, leagues: 0 },
      updated: { drivers: 0, teams: 0, leagues: 0 },
      errors: []
    };

    try {
      const logger = new MediaMigrationLogger();

      // Create in-memory repositories
      const driverRepo = new InMemoryDriverRepository(logger);
      const teamRepo = new InMemoryTeamRepository(logger);
      const leagueRepo = new InMemoryLeagueRepository(logger);
      const mediaRepo = new InMemoryMediaRepository(logger);

      // Get all data
      const drivers = await driverRepo.findAll();
      const teams = await teamRepo.findAll();
      const leagues = await leagueRepo.findAll();

      result.processed.drivers = drivers.length;
      result.processed.teams = teams.length;
      result.processed.leagues = leagues.length;

      this.logger.info(`In-memory mode: Found ${drivers.length} drivers, ${teams.length} teams, ${leagues.length} leagues`);

      // Migrate drivers
      for (const driver of drivers) {
        try {
          // Check if already has valid MediaReference
          if (driver.avatarRef && driver.avatarRef instanceof MediaReference) {
            continue;
          }

          // Get old URL from in-memory media repo
          const oldUrl = await mediaRepo.getDriverAvatar(driver.id);
          
          if (oldUrl) {
            const newRef = this.parseOldUrl(oldUrl);
            if (newRef) {
              // Update driver in repository
              const updated = driver.update({ avatarRef: newRef });
              if (!this.dryRun) {
                await driverRepo.update(updated);
                result.updated.drivers++;
              } else {
                result.updated.drivers++;
              }
            }
          }
        } catch (error) {
          result.errors.push(`Driver ${driver.id}: ${error instanceof Error ? error.message : String(error)}`);
        }
      }

      // Migrate teams
      for (const team of teams) {
        try {
          if (team.logoRef && team.logoRef instanceof MediaReference) {
            continue;
          }

          const oldUrl = await mediaRepo.getTeamLogo(team.id);
          
          if (oldUrl) {
            const newRef = this.parseOldUrl(oldUrl);
            if (newRef) {
              const updated = team.update({ logoRef: newRef });
              if (!this.dryRun) {
                await teamRepo.update(updated);
                result.updated.teams++;
              } else {
                result.updated.teams++;
              }
            }
          }
        } catch (error) {
          result.errors.push(`Team ${team.id}: ${error instanceof Error ? error.message : String(error)}`);
        }
      }

      // Migrate leagues
      for (const league of leagues) {
        try {
          if (league.logoRef && league.logoRef instanceof MediaReference) {
            continue;
          }

          const oldUrl = await mediaRepo.getLeagueLogo(league.id);
          
          if (oldUrl) {
            const newRef = this.parseOldUrl(oldUrl);
            if (newRef) {
              const updated = league.update({ logoRef: newRef });
              if (!this.dryRun) {
                await leagueRepo.update(updated);
                result.updated.leagues++;
              } else {
                result.updated.leagues++;
              }
            }
          }
        } catch (error) {
          result.errors.push(`League ${league.id}: ${error instanceof Error ? error.message : String(error)}`);
        }
      }

      result.success = true;
      this.logger.info(`In-memory migration completed: ${result.updated.drivers} drivers, ${result.updated.teams} teams, ${result.updated.leagues} leagues updated`);

      return result;

    } catch (error) {
      result.errors.push(error instanceof Error ? error.message : String(error));
      this.logger.error('In-memory migration failed', error instanceof Error ? error.stack : undefined);
      return result;
    }
  }

  /**
   * Run migration based on environment
   */
  async run(): Promise<MigrationResult> {
    const persistence = process.env.GRIDPILOT_API_PERSISTENCE || 
                       (process.env.DATABASE_URL ? 'postgres' : 'inmemory');

    this.logger.info(`Starting media reference migration in ${this.dryRun ? 'DRY RUN' : 'EXECUTE'} mode`);
    this.logger.info(`Persistence mode: ${persistence}`);

    if (this.dryRun) {
      this.logger.info('DRY RUN: No changes will be made to the database');
    }

    if (persistence === 'postgres') {
      return this.migratePostgres();
    } else {
      return this.migrateInMemory();
    }
  }
}

/**
 * CLI entry point
 */
async function main() {
  const args = process.argv.slice(2);
  const dryRun = !args.includes('--execute') && !args.includes('-e');

  if (args.includes('--help') || args.includes('-h')) {
    console.log(`
Media Reference Migration Script

Usage:
  ts-node scripts/migrate-media-refs.ts [options]

Options:
  --execute, -e    Execute the migration (default: dry run)
  --help, -h       Show this help message

Environment:
  GRIDPILOT_API_PERSISTENCE=postgres|inmemory  (default: postgres if DATABASE_URL set)
  DATABASE_URL                                 (required for postgres mode)

Examples:
  # Dry run (test mode)
  ts-node scripts/migrate-media-refs.ts
  
  # Execute migration
  ts-node scripts/migrate-media-refs.ts --execute
  
  # With specific persistence
  GRIDPILOT_API_PERSISTENCE=inmemory ts-node scripts/migrate-media-refs.ts --execute
    `);
    process.exit(0);
  }

  const migration = new MediaMigration(dryRun);
  const result = await migration.run();

  // Print summary
  console.log('\n=== Migration Summary ===');
  console.log(`Mode: ${dryRun ? 'DRY RUN' : 'EXECUTED'}`);
  console.log(`Processed: ${result.processed.drivers} drivers, ${result.processed.teams} teams, ${result.processed.leagues} leagues`);
  console.log(`Updated: ${result.updated.drivers} drivers, ${result.updated.teams} teams, ${result.updated.leagues} leagues`);
  
  if (result.errors.length > 0) {
    console.log(`\nErrors (${result.errors.length}):`);
    result.errors.forEach(err => console.log(`  - ${err}`));
  }

  if (!result.success) {
    console.log('\n❌ Migration failed');
    process.exit(1);
  }

  if (dryRun) {
    console.log('\n✅ Dry run completed successfully. Run with --execute to apply changes.');
  } else {
    console.log('\n✅ Migration completed successfully.');
  }

  process.exit(0);
}

// Run if called directly
if (require.main === module) {
  main().catch(error => {
    console.error('Fatal error:', error);
    process.exit(1);
  });
}