import 'dart:io';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import '../models/session.dart';
import '../models/target_analysis.dart';
import '../models/shot.dart';
import '../models/weapon.dart';
import '../models/maintenance.dart';
import '../../core/constants/app_constants.dart';

class DatabaseHelper {
  static DatabaseHelper? _instance;
  static Database? _database;

  DatabaseHelper._internal();

  factory DatabaseHelper() {
    _instance ??= DatabaseHelper._internal();
    return _instance!;
  }

  Future<Database> get database async {
    _database ??= await _initDatabase();
    return _database!;
  }

  Future<Database> _initDatabase() async {
    final databasesPath = await getDatabasesPath();
    final path = join(databasesPath, AppConstants.databaseName);

    return await openDatabase(
      path,
      version: AppConstants.databaseVersion,
      onCreate: _onCreate,
      onUpgrade: _onUpgrade,
    );
  }

  Future<void> _onCreate(Database db, int version) async {
    await db.execute('''
      CREATE TABLE ${AppConstants.sessionsTable} (
        id TEXT PRIMARY KEY,
        weapon TEXT NOT NULL,
        weapon_id TEXT,
        max_shots_per_target INTEGER NOT NULL,
        created_at TEXT NOT NULL,
        notes TEXT,
        distance INTEGER DEFAULT 25
      )
    ''');

    await db.execute('''
      CREATE TABLE ${AppConstants.targetAnalysesTable} (
        id TEXT PRIMARY KEY,
        session_id TEXT NOT NULL,
        target_type TEXT NOT NULL,
        image_path TEXT NOT NULL,
        total_score INTEGER NOT NULL,
        grouping_diameter REAL,
        grouping_center_x REAL,
        grouping_center_y REAL,
        created_at TEXT NOT NULL,
        notes TEXT,
        target_center_x REAL,
        target_center_y REAL,
        target_radius REAL,
        FOREIGN KEY (session_id) REFERENCES ${AppConstants.sessionsTable}(id) ON DELETE CASCADE
      )
    ''');

    await db.execute('''
      CREATE TABLE ${AppConstants.shotsTable} (
        id TEXT PRIMARY KEY,
        x REAL NOT NULL,
        y REAL NOT NULL,
        score INTEGER NOT NULL,
        analysis_id TEXT NOT NULL,
        FOREIGN KEY (analysis_id) REFERENCES ${AppConstants.targetAnalysesTable}(id) ON DELETE CASCADE
      )
    ''');

    await db.execute('''
      CREATE INDEX idx_target_analyses_session_id ON ${AppConstants.targetAnalysesTable}(session_id)
    ''');

    await db.execute('''
      CREATE INDEX idx_shots_analysis_id ON ${AppConstants.shotsTable}(analysis_id)
    ''');

    await db.execute('''
      CREATE INDEX idx_sessions_created_at ON ${AppConstants.sessionsTable}(created_at DESC)
    ''');

    await db.execute('''
      CREATE TABLE ${AppConstants.weaponsTable} (
        id TEXT PRIMARY KEY,
        name TEXT NOT NULL,
        type TEXT NOT NULL,
        caliber TEXT NOT NULL,
        magazine_count INTEGER NOT NULL,
        magazine_capacity INTEGER NOT NULL,
        notes TEXT,
        created_at TEXT NOT NULL,
        optic TEXT,
        silencer TEXT,
        trigger TEXT,
        custom_name TEXT
      )
    ''');

    await db.execute('''
      CREATE TABLE ${AppConstants.maintenanceTable} (
        id TEXT PRIMARY KEY,
        weapon_id TEXT NOT NULL,
        type TEXT NOT NULL,
        description TEXT NOT NULL,
        date TEXT NOT NULL,
        rounds_since_last INTEGER,
        FOREIGN KEY (weapon_id) REFERENCES ${AppConstants.weaponsTable}(id) ON DELETE CASCADE
      )
    ''');
  }

  Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
    if (oldVersion < 2) {
      // Migration from single-target sessions to multi-target sessions

      // 1. Rename existing sessions table to a temporary name
      await db.execute('ALTER TABLE ${AppConstants.sessionsTable} RENAME TO sessions_old');

      // 2. Create the new sessions table
      await db.execute('''
        CREATE TABLE ${AppConstants.sessionsTable} (
          id TEXT PRIMARY KEY,
          weapon TEXT NOT NULL,
          max_shots_per_target INTEGER NOT NULL,
          created_at TEXT NOT NULL,
          notes TEXT
        )
      ''');

      // 3. Create the new target_analyses table
      await db.execute('''
        CREATE TABLE ${AppConstants.targetAnalysesTable} (
          id TEXT PRIMARY KEY,
          session_id TEXT NOT NULL,
          target_type TEXT NOT NULL,
          image_path TEXT NOT NULL,
          total_score INTEGER NOT NULL,
          grouping_diameter REAL,
          grouping_center_x REAL,
          grouping_center_y REAL,
          created_at TEXT NOT NULL,
          notes TEXT,
          target_center_x REAL,
          target_center_y REAL,
          target_radius REAL,
          FOREIGN KEY (session_id) REFERENCES ${AppConstants.sessionsTable}(id) ON DELETE CASCADE
        )
      ''');

      // 4. Update shots table (rename session_id to analysis_id)
      await db.execute('ALTER TABLE ${AppConstants.shotsTable} RENAME TO shots_old');
      await db.execute('''
        CREATE TABLE ${AppConstants.shotsTable} (
          id TEXT PRIMARY KEY,
          x REAL NOT NULL,
          y REAL NOT NULL,
          score INTEGER NOT NULL,
          analysis_id TEXT NOT NULL,
          FOREIGN KEY (analysis_id) REFERENCES ${AppConstants.targetAnalysesTable}(id) ON DELETE CASCADE
        )
      ''');

      // 5. Migrate data
      // For each old session, create one session and one target_analysis
      final oldSessions = await db.query('sessions_old');
      for (final old in oldSessions) {
        final sessionId = old['id'] as String;
        final analysisId = 'analysis_$sessionId'; // Create a unique ID for the analysis

        // Insert into new sessions
        await db.insert(AppConstants.sessionsTable, {
          'id': sessionId,
          'weapon': 'Inconnue',
          'max_shots_per_target': 5,
          'created_at': old['created_at'],
          'notes': old['notes'],
        });

        // Insert into target_analyses
        await db.insert(AppConstants.targetAnalysesTable, {
          'id': analysisId,
          'session_id': sessionId,
          'target_type': old['target_type'],
          'image_path': old['image_path'],
          'total_score': old['total_score'],
          'grouping_diameter': old['grouping_diameter'],
          'grouping_center_x': old['grouping_center_x'],
          'grouping_center_y': old['grouping_center_y'],
          'created_at': old['created_at'],
          'notes': null,
          'target_center_x': old['target_center_x'],
          'target_center_y': old['target_center_y'],
          'target_radius': old['target_radius'],
        });

        // Migrate shots from shots_old to shots
        final oldShots = await db.query('shots_old', where: 'session_id = ?', whereArgs: [sessionId]);
        for (final shot in oldShots) {
          await db.insert(AppConstants.shotsTable, {
            'id': shot['id'],
            'x': shot['x'],
            'y': shot['y'],
            'score': shot['score'],
            'analysis_id': analysisId,
          });
        }
      }

      // 6. Cleanup
      await db.execute('DROP TABLE sessions_old');
      await db.execute('DROP TABLE shots_old');

      // 7. Re-create indexes
      await db.execute('''
        CREATE INDEX idx_target_analyses_session_id ON ${AppConstants.targetAnalysesTable}(session_id)
      ''');
      await db.execute('''
        CREATE INDEX idx_shots_analysis_id ON ${AppConstants.shotsTable}(analysis_id)
      ''');
      await db.execute('''
        CREATE INDEX idx_sessions_created_at ON ${AppConstants.sessionsTable}(created_at DESC)
      ''');
    }

    if (oldVersion < 3) {
      // Add weapons table
      await db.execute('''
        CREATE TABLE ${AppConstants.weaponsTable} (
          id TEXT PRIMARY KEY,
          name TEXT NOT NULL,
          type TEXT NOT NULL,
          caliber TEXT NOT NULL,
          magazine_count INTEGER NOT NULL,
          magazine_capacity INTEGER NOT NULL,
          notes TEXT,
          created_at TEXT NOT NULL
        )
      ''');

      // Add weapon_id to sessions table
      await db.execute('ALTER TABLE ${AppConstants.sessionsTable} ADD COLUMN weapon_id TEXT');
    }

    if (oldVersion < 4) {
      // Add maintenance table
      await db.execute('''
        CREATE TABLE ${AppConstants.maintenanceTable} (
          id TEXT PRIMARY KEY,
          weapon_id TEXT NOT NULL,
          type TEXT NOT NULL,
          description TEXT NOT NULL,
          date TEXT NOT NULL,
          rounds_since_last INTEGER,
          FOREIGN KEY (weapon_id) REFERENCES ${AppConstants.weaponsTable}(id) ON DELETE CASCADE
        )
      ''');
    }

    if (oldVersion < 5) {
      // Add new weapon options columns
      await db.execute('ALTER TABLE ${AppConstants.weaponsTable} ADD COLUMN optic TEXT');
      await db.execute('ALTER TABLE ${AppConstants.weaponsTable} ADD COLUMN silencer TEXT');
      await db.execute('ALTER TABLE ${AppConstants.weaponsTable} ADD COLUMN "trigger" TEXT');
      await db.execute('ALTER TABLE ${AppConstants.weaponsTable} ADD COLUMN custom_name TEXT');
    }

    if (oldVersion < 6) {
      // Add distance column to sessions
      await db.execute('ALTER TABLE ${AppConstants.sessionsTable} ADD COLUMN distance INTEGER DEFAULT 25');
    }
  }

  // Session operations
  Future<int> insertSession(Session session) async {
    final db = await database;
    return await db.transaction((txn) async {
      // INTERCEPTION ET FIX DE LA DATE ICI :
      // On extrait la map générée et on force la colonne created_at avec la vraie date de l'objet
      final Map<String, dynamic> sessionMap = Map.from(session.toMap());
      sessionMap['created_at'] = session.createdAt.toIso8601String();

      await txn.insert(
        AppConstants.sessionsTable,
        sessionMap, // On utilise notre map corrigée au lieu de session.toMap() direct !
        conflictAlgorithm: ConflictAlgorithm.replace,
      );

      for (final analysis in session.analyses) {
        await insertTargetAnalysisTx(txn, analysis, session.id);
      }

      return 1;
    });
  }

  Future<void> insertTargetAnalysis(TargetAnalysis analysis, String sessionId) async {
    final db = await database;
    await db.transaction((txn) async {
      await insertTargetAnalysisTx(txn, analysis, sessionId);
    });
  }

  Future<void> insertTargetAnalysisTx(Transaction txn, TargetAnalysis analysis, String sessionId) async {
    final analysisWithSessionId = analysis.copyWith(sessionId: sessionId);
    await txn.insert(
      AppConstants.targetAnalysesTable,
      analysisWithSessionId.toMap(),
      conflictAlgorithm: ConflictAlgorithm.replace,
    );

    for (final shot in analysis.shots) {
      final shotWithAnalysisId = shot.copyWith(analysisId: analysis.id);
      await txn.insert(
        AppConstants.shotsTable,
        shotWithAnalysisId.toMap(),
        conflictAlgorithm: ConflictAlgorithm.replace,
      );
    }
  }

  Future<Session?> getSession(String id) async {
    final db = await database;
    final sessionMaps = await db.query(
      AppConstants.sessionsTable,
      where: 'id = ?',
      whereArgs: [id],
    );

    if (sessionMaps.isEmpty) return null;

    final analysisMaps = await db.query(
      AppConstants.targetAnalysesTable,
      where: 'session_id = ?',
      whereArgs: [id],
    );

    final analyses = <TargetAnalysis>[];
    for (final analysisMap in analysisMaps) {
      final analysisId = analysisMap['id'] as String;
      final shotMaps = await db.query(
        AppConstants.shotsTable,
        where: 'analysis_id = ?',
        whereArgs: [analysisId],
      );
      final shots = shotMaps.map((map) => Shot.fromMap(map)).toList();
      analyses.add(TargetAnalysis.fromMap(analysisMap, shots));
    }

    return Session.fromMap(sessionMaps.first, analyses);
  }

  Future<List<Session>> getAllSessions({
    int? limit,
    int? offset,
  }) async {
    final db = await database;

    final sessionMaps = await db.query(
      AppConstants.sessionsTable,
      orderBy: 'created_at DESC',
      limit: limit,
      offset: offset,
    );

    final sessions = <Session>[];
    for (final sessionMap in sessionMaps) {
      final sessionId = sessionMap['id'] as String;
      final session = await getSession(sessionId);
      if (session != null) {
        sessions.add(session);
      }
    }

    return sessions;
  }

  Future<int> deleteSession(String id) async {
    final db = await database;
    return await db.delete(
      AppConstants.sessionsTable,
      where: 'id = ?',
      whereArgs: [id],
    );
  }

  Future<Map<String, dynamic>> getStatistics() async {
    final db = await database;

    final totalSessions = Sqflite.firstIntValue(
      await db.rawQuery('SELECT COUNT(*) FROM ${AppConstants.sessionsTable}'),
    ) ?? 0;

    final totalShots = Sqflite.firstIntValue(
      await db.rawQuery('SELECT COUNT(*) FROM ${AppConstants.shotsTable}'),
    ) ?? 0;

    final avgScore = (await db.rawQuery(
      'SELECT AVG(total_score) as avg FROM ${AppConstants.targetAnalysesTable}',
    )).first['avg'] as double? ?? 0.0;

    final bestScore = Sqflite.firstIntValue(
      await db.rawQuery(
        'SELECT MAX(total_score) FROM ${AppConstants.targetAnalysesTable}',
      ),
    ) ?? 0;

    return {
      'totalSessions': totalSessions,
      'totalShots': totalShots,
      'averageScore': avgScore,
      'bestScore': bestScore,
    };
  }

  // Weapon operations
  Future<int> insertWeapon(Weapon weapon) async {
    final db = await database;
    return await db.insert(
      AppConstants.weaponsTable,
      weapon.toMap(),
      conflictAlgorithm: ConflictAlgorithm.replace,
    );
  }

  Future<List<Weapon>> getAllWeapons() async {
    final db = await database;
    final List<Map<String, dynamic>> maps = await db.query(AppConstants.weaponsTable, orderBy: 'name ASC');
    return List.generate(maps.length, (i) => Weapon.fromMap(maps[i]));
  }

  Future<Weapon?> getWeapon(String id) async {
    final db = await database;
    final List<Map<String, dynamic>> maps = await db.query(
      AppConstants.weaponsTable,
      where: 'id = ?',
      whereArgs: [id],
    );
    if (maps.isEmpty) return null;
    return Weapon.fromMap(maps.first);
  }

  Future<int> updateWeapon(Weapon weapon) async {
    final db = await database;

    // Start a transaction to ensure both updates succeed
    return await db.transaction((txn) async {
      // 1. Update weapon in armory
      final count = await txn.update(
        AppConstants.weaponsTable,
        weapon.toMap(),
        where: 'id = ?',
        whereArgs: [weapon.id],
      );

      // 2. Propagate name change to all sessions linked to this weapon
      await txn.update(
        AppConstants.sessionsTable,
        {'weapon': weapon.displayName},
        where: 'weapon_id = ?',
        whereArgs: [weapon.id],
      );

      return count;
    });
  }

  Future<int> deleteWeapon(String id) async {
    final db = await database;

    return await db.transaction((txn) async {
      // 1. Unlink sessions from this weapon but KEEP the name (gravé à jamais)
      await txn.update(
        AppConstants.sessionsTable,
        {'weapon_id': null},
        where: 'weapon_id = ?',
        whereArgs: [id],
      );

      // 2. Delete the weapon record
      return await txn.delete(
        AppConstants.weaponsTable,
        where: 'id = ?',
        whereArgs: [id],
      );
    });
  }

  // Weapon statistics & Maintenance
  Future<int> getRoundsFiredForWeapon(String weaponId) async {
    final db = await database;
    final result = await db.rawQuery('''
      SELECT COUNT(s.id) as count 
      FROM ${AppConstants.shotsTable} s
      JOIN ${AppConstants.targetAnalysesTable} ta ON s.analysis_id = ta.id
      JOIN ${AppConstants.sessionsTable} sess ON ta.session_id = sess.id
      WHERE sess.weapon_id = ?
    ''', [weaponId]);
    return Sqflite.firstIntValue(result) ?? 0;
  }

  Future<int> insertMaintenance(MaintenanceEntry entry) async {
    final db = await database;
    return await db.insert(
      AppConstants.maintenanceTable,
      entry.toMap(),
      conflictAlgorithm: ConflictAlgorithm.replace,
    );
  }

  Future<List<MaintenanceEntry>> getMaintenanceForWeapon(String weaponId) async {
    final db = await database;
    final maps = await db.query(
      AppConstants.maintenanceTable,
      where: 'weapon_id = ?',
      orderBy: 'date DESC',
      whereArgs: [weaponId],
    );
    return List.generate(maps.length, (i) => MaintenanceEntry.fromMap(maps[i]));
  }

  Future<int> deleteMaintenance(String id) async {
    final db = await database;
    return await db.delete(
      AppConstants.maintenanceTable,
      where: 'id = ?',
      whereArgs: [id],
    );
  }

  Future<void> close() async {
    final db = await database;
    await db.close();
    _database = null;
  }
}