// ABOUTME: Database service for managing SQLite database operations in the health tracker
// ABOUTME: Handles database initialization, table creation, CRUD operations, and migration support
import 'dart:async';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import 'package:albi/models/health_data.dart';
import 'package:albi/models/weight_entry.dart';
import 'package:albi/models/blood_pressure_entry.dart';
import 'package:albi/models/mood_entry.dart';
import 'package:albi/utils/constants.dart';

/// Service class for managing all database operations
class DatabaseService {
  static DatabaseService? _instance;
  static Database? _database;

  // Private constructor for singleton pattern
  DatabaseService._();

  /// Get singleton instance of DatabaseService
  static DatabaseService get instance {
    _instance ??= DatabaseService._();
    return _instance!;
  }

  /// Get database instance, initializing if necessary
  Future<Database> get database async {
    if (_database != null) return _database!;
    _database = await _initDatabase();
    return _database!;
  }

  /// Initialize the SQLite database
  Future<Database> _initDatabase() async {
    final dbPath = await getDatabasesPath();
    final path = join(dbPath, AppConstants.databaseName);

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

  /// Configure database settings (enable foreign keys, etc.)
  Future<void> _onConfigure(Database db) async {
    await db.execute('PRAGMA foreign_keys = ON');
  }

  /// Create database tables on first initialization
  Future<void> _createTables(Database db, int version) async {
    // Create weight entries table
    await db.execute('''
      CREATE TABLE ${AppConstants.weightEntriesTable} (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        timestamp TEXT NOT NULL,
        notes TEXT,
        weight_kg REAL NOT NULL,
        original_unit TEXT NOT NULL DEFAULT 'kg'
      )
    ''');

    // Create blood pressure entries table
    await db.execute('''
      CREATE TABLE ${AppConstants.bloodPressureEntriesTable} (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        timestamp TEXT NOT NULL,
        notes TEXT,
        systolic INTEGER NOT NULL,
        diastolic INTEGER NOT NULL
      )
    ''');

    // Create mood entries table
    await db.execute('''
      CREATE TABLE ${AppConstants.moodEntriesTable} (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        timestamp TEXT NOT NULL,
        notes TEXT,
        mood TEXT NOT NULL
      )
    ''');

    // Create indices for better query performance
    await _createIndices(db);
  }

  /// Create database indices for optimal query performance
  Future<void> _createIndices(Database db) async {
    await db.execute('CREATE INDEX idx_weight_timestamp ON ${AppConstants.weightEntriesTable}(timestamp)');
    await db.execute('CREATE INDEX idx_bp_timestamp ON ${AppConstants.bloodPressureEntriesTable}(timestamp)');
    await db.execute('CREATE INDEX idx_mood_timestamp ON ${AppConstants.moodEntriesTable}(timestamp)');
  }

  /// Handle database schema upgrades
  Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
    // Future migrations will be handled here
    // For now, we only have version 1, so no migrations needed
  }

  // Weight Entry CRUD Operations

  /// Insert a new weight entry
  Future<int> insertWeightEntry(WeightEntry entry) async {
    final db = await database;
    final map = entry.toJson();
    map.remove('id'); // Let database assign ID
    return await db.insert(AppConstants.weightEntriesTable, map);
  }

  /// Get all weight entries, optionally filtered by date range
  Future<List<WeightEntry>> getWeightEntries({
    DateTime? startDate,
    DateTime? endDate,
    int? limit,
  }) async {
    final db = await database;
    String whereClause = '';
    List<dynamic> whereArgs = [];

    if (startDate != null || endDate != null) {
      List<String> conditions = [];
      if (startDate != null) {
        conditions.add('timestamp >= ?');
        whereArgs.add(startDate.toIso8601String());
      }
      if (endDate != null) {
        conditions.add('timestamp <= ?');
        whereArgs.add(endDate.toIso8601String());
      }
      whereClause = 'WHERE ${conditions.join(' AND ')}';
    }

    final limitClause = limit != null ? 'LIMIT $limit' : '';
    final query = '''
      SELECT * FROM ${AppConstants.weightEntriesTable} 
      $whereClause 
      ORDER BY timestamp DESC 
      $limitClause
    ''';

    final maps = await db.rawQuery(query, whereArgs);
    return maps.map((map) => WeightEntry.fromJson(map)).toList();
  }

  /// Get a specific weight entry by ID
  Future<WeightEntry?> getWeightEntry(int id) async {
    final db = await database;
    final maps = await db.query(
      AppConstants.weightEntriesTable,
      where: 'id = ?',
      whereArgs: [id],
    );
    
    if (maps.isEmpty) return null;
    return WeightEntry.fromJson(maps.first);
  }

  /// Update an existing weight entry
  Future<int> updateWeightEntry(WeightEntry entry) async {
    final db = await database;
    return await db.update(
      AppConstants.weightEntriesTable,
      entry.toJson(),
      where: 'id = ?',
      whereArgs: [entry.id],
    );
  }

  /// Delete a weight entry
  Future<int> deleteWeightEntry(int id) async {
    final db = await database;
    return await db.delete(
      AppConstants.weightEntriesTable,
      where: 'id = ?',
      whereArgs: [id],
    );
  }

  // Blood Pressure Entry CRUD Operations

  /// Insert a new blood pressure entry
  Future<int> insertBloodPressureEntry(BloodPressureEntry entry) async {
    final db = await database;
    final map = entry.toJson();
    map.remove('id'); // Let database assign ID
    return await db.insert(AppConstants.bloodPressureEntriesTable, map);
  }

  /// Get all blood pressure entries, optionally filtered by date range
  Future<List<BloodPressureEntry>> getBloodPressureEntries({
    DateTime? startDate,
    DateTime? endDate,
    int? limit,
  }) async {
    final db = await database;
    String whereClause = '';
    List<dynamic> whereArgs = [];

    if (startDate != null || endDate != null) {
      List<String> conditions = [];
      if (startDate != null) {
        conditions.add('timestamp >= ?');
        whereArgs.add(startDate.toIso8601String());
      }
      if (endDate != null) {
        conditions.add('timestamp <= ?');
        whereArgs.add(endDate.toIso8601String());
      }
      whereClause = 'WHERE ${conditions.join(' AND ')}';
    }

    final limitClause = limit != null ? 'LIMIT $limit' : '';
    final query = '''
      SELECT * FROM ${AppConstants.bloodPressureEntriesTable} 
      $whereClause 
      ORDER BY timestamp DESC 
      $limitClause
    ''';

    final maps = await db.rawQuery(query, whereArgs);
    return maps.map((map) => BloodPressureEntry.fromJson(map)).toList();
  }

  /// Get a specific blood pressure entry by ID
  Future<BloodPressureEntry?> getBloodPressureEntry(int id) async {
    final db = await database;
    final maps = await db.query(
      AppConstants.bloodPressureEntriesTable,
      where: 'id = ?',
      whereArgs: [id],
    );
    
    if (maps.isEmpty) return null;
    return BloodPressureEntry.fromJson(maps.first);
  }

  /// Update an existing blood pressure entry
  Future<int> updateBloodPressureEntry(BloodPressureEntry entry) async {
    final db = await database;
    return await db.update(
      AppConstants.bloodPressureEntriesTable,
      entry.toJson(),
      where: 'id = ?',
      whereArgs: [entry.id],
    );
  }

  /// Delete a blood pressure entry
  Future<int> deleteBloodPressureEntry(int id) async {
    final db = await database;
    return await db.delete(
      AppConstants.bloodPressureEntriesTable,
      where: 'id = ?',
      whereArgs: [id],
    );
  }

  // Mood Entry CRUD Operations

  /// Insert a new mood entry
  Future<int> insertMoodEntry(MoodEntry entry) async {
    final db = await database;
    final map = entry.toJson();
    map.remove('id'); // Let database assign ID
    return await db.insert(AppConstants.moodEntriesTable, map);
  }

  /// Get all mood entries, optionally filtered by date range
  Future<List<MoodEntry>> getMoodEntries({
    DateTime? startDate,
    DateTime? endDate,
    int? limit,
  }) async {
    final db = await database;
    String whereClause = '';
    List<dynamic> whereArgs = [];

    if (startDate != null || endDate != null) {
      List<String> conditions = [];
      if (startDate != null) {
        conditions.add('timestamp >= ?');
        whereArgs.add(startDate.toIso8601String());
      }
      if (endDate != null) {
        conditions.add('timestamp <= ?');
        whereArgs.add(endDate.toIso8601String());
      }
      whereClause = 'WHERE ${conditions.join(' AND ')}';
    }

    final limitClause = limit != null ? 'LIMIT $limit' : '';
    final query = '''
      SELECT * FROM ${AppConstants.moodEntriesTable} 
      $whereClause 
      ORDER BY timestamp DESC 
      $limitClause
    ''';

    final maps = await db.rawQuery(query, whereArgs);
    return maps.map((map) => MoodEntry.fromJson(map)).toList();
  }

  /// Get a specific mood entry by ID
  Future<MoodEntry?> getMoodEntry(int id) async {
    final db = await database;
    final maps = await db.query(
      AppConstants.moodEntriesTable,
      where: 'id = ?',
      whereArgs: [id],
    );
    
    if (maps.isEmpty) return null;
    return MoodEntry.fromJson(maps.first);
  }

  /// Update an existing mood entry
  Future<int> updateMoodEntry(MoodEntry entry) async {
    final db = await database;
    return await db.update(
      AppConstants.moodEntriesTable,
      entry.toJson(),
      where: 'id = ?',
      whereArgs: [entry.id],
    );
  }

  /// Delete a mood entry
  Future<int> deleteMoodEntry(int id) async {
    final db = await database;
    return await db.delete(
      AppConstants.moodEntriesTable,
      where: 'id = ?',
      whereArgs: [id],
    );
  }

  // Aggregation and Analytics Methods

  /// Get recent entries across all data types for dashboard
  Future<Map<String, List<HealthData>>> getRecentEntries({int limit = 5}) async {
    final results = <String, List<HealthData>>{};
    
    results['weight'] = (await getWeightEntries(limit: limit)).cast<HealthData>();
    results['bloodPressure'] = (await getBloodPressureEntries(limit: limit)).cast<HealthData>();
    results['mood'] = (await getMoodEntries(limit: limit)).cast<HealthData>();
    
    return results;
  }

  /// Get count of entries for each data type
  Future<Map<String, int>> getEntryCounts() async {
    final db = await database;
    final results = <String, int>{};
    
    final weightCount = Sqflite.firstIntValue(
      await db.rawQuery('SELECT COUNT(*) FROM ${AppConstants.weightEntriesTable}')
    ) ?? 0;
    
    final bpCount = Sqflite.firstIntValue(
      await db.rawQuery('SELECT COUNT(*) FROM ${AppConstants.bloodPressureEntriesTable}')
    ) ?? 0;
    
    final moodCount = Sqflite.firstIntValue(
      await db.rawQuery('SELECT COUNT(*) FROM ${AppConstants.moodEntriesTable}')
    ) ?? 0;
    
    results['weight'] = weightCount;
    results['bloodPressure'] = bpCount;
    results['mood'] = moodCount;
    
    return results;
  }

  /// Get average weight for a given period
  Future<double?> getAverageWeight({DateTime? startDate, DateTime? endDate}) async {
    final db = await database;
    String whereClause = '';
    List<dynamic> whereArgs = [];

    if (startDate != null || endDate != null) {
      List<String> conditions = [];
      if (startDate != null) {
        conditions.add('timestamp >= ?');
        whereArgs.add(startDate.toIso8601String());
      }
      if (endDate != null) {
        conditions.add('timestamp <= ?');
        whereArgs.add(endDate.toIso8601String());
      }
      whereClause = 'WHERE ${conditions.join(' AND ')}';
    }

    final query = 'SELECT AVG(weight_kg) FROM ${AppConstants.weightEntriesTable} $whereClause';
    final result = await db.rawQuery(query, whereArgs);
    
    return result.first['AVG(weight_kg)'] as double?;
  }

  /// Get latest weight entry for trends
  Future<WeightEntry?> getLatestWeightEntry() async {
    final entries = await getWeightEntries(limit: 1);
    return entries.isNotEmpty ? entries.first : null;
  }

  /// Get weight trend (difference from previous entry)
  Future<double?> getWeightTrend() async {
    final entries = await getWeightEntries(limit: 2);
    if (entries.length < 2) return null;
    
    return entries.first.weightKg - entries[1].weightKg;
  }

  /// Get latest blood pressure entry for trends
  Future<BloodPressureEntry?> getLatestBloodPressureEntry() async {
    final entries = await getBloodPressureEntries(limit: 1);
    return entries.isNotEmpty ? entries.first : null;
  }

  /// Get latest mood entry for trends
  Future<MoodEntry?> getLatestMoodEntry() async {
    final entries = await getMoodEntries(limit: 1);
    return entries.isNotEmpty ? entries.first : null;
  }

  /// Check for duplicate weight entries (within 5 minutes of same weight)
  Future<List<WeightEntry>> findDuplicateWeightEntries(WeightEntry entry) async {
    final db = await database;
    final timestamp = entry.timestamp;
    final startTime = timestamp.subtract(const Duration(minutes: 5));
    final endTime = timestamp.add(const Duration(minutes: 5));
    
    final maps = await db.query(
      AppConstants.weightEntriesTable,
      where: 'weight_kg = ? AND timestamp BETWEEN ? AND ? AND id != ?',
      whereArgs: [entry.weightKg, startTime.toIso8601String(), endTime.toIso8601String(), entry.id ?? -1],
    );
    
    return maps.map((map) => WeightEntry.fromJson(map)).toList();
  }

  /// Check for duplicate blood pressure entries (within 5 minutes of same values)
  Future<List<BloodPressureEntry>> findDuplicateBloodPressureEntries(BloodPressureEntry entry) async {
    final db = await database;
    final timestamp = entry.timestamp;
    final startTime = timestamp.subtract(const Duration(minutes: 5));
    final endTime = timestamp.add(const Duration(minutes: 5));
    
    final maps = await db.query(
      AppConstants.bloodPressureEntriesTable,
      where: 'systolic = ? AND diastolic = ? AND timestamp BETWEEN ? AND ? AND id != ?',
      whereArgs: [entry.systolic, entry.diastolic, startTime.toIso8601String(), endTime.toIso8601String(), entry.id ?? -1],
    );
    
    return maps.map((map) => BloodPressureEntry.fromJson(map)).toList();
  }

  /// Check for duplicate mood entries (within 5 minutes of same mood)
  Future<List<MoodEntry>> findDuplicateMoodEntries(MoodEntry entry) async {
    final db = await database;
    final timestamp = entry.timestamp;
    final startTime = timestamp.subtract(const Duration(minutes: 5));
    final endTime = timestamp.add(const Duration(minutes: 5));
    
    final maps = await db.query(
      AppConstants.moodEntriesTable,
      where: 'mood = ? AND timestamp BETWEEN ? AND ? AND id != ?',
      whereArgs: [entry.mood.toString().split('.').last, startTime.toIso8601String(), endTime.toIso8601String(), entry.id ?? -1],
    );
    
    return maps.map((map) => MoodEntry.fromJson(map)).toList();
  }

  /// Merge duplicate entries by keeping the latest one and deleting duplicates
  Future<void> mergeDuplicateWeightEntries(List<WeightEntry> duplicates) async {
    if (duplicates.isEmpty) return;
    
    final db = await database;
    await db.transaction((txn) async {
      // Keep the latest entry (first in list due to ORDER BY timestamp DESC)
      final keepEntry = duplicates.first;
      
      // Delete the rest
      for (int i = 1; i < duplicates.length; i++) {
        await txn.delete(
          AppConstants.weightEntriesTable,
          where: 'id = ?',
          whereArgs: [duplicates[i].id],
        );
      }
    });
  }

  // Batch Operations

  /// Insert multiple entries in a transaction
  Future<void> batchInsert(List<HealthData> entries) async {
    final db = await database;
    await db.transaction((txn) async {
      for (final entry in entries) {
        final map = entry.toJson();
        map.remove('id'); // Let database assign ID
        
        if (entry is WeightEntry) {
          await txn.insert(AppConstants.weightEntriesTable, map);
        } else if (entry is BloodPressureEntry) {
          await txn.insert(AppConstants.bloodPressureEntriesTable, map);
        } else if (entry is MoodEntry) {
          await txn.insert(AppConstants.moodEntriesTable, map);
        }
      }
    });
  }

  /// Clear all data (for testing or reset purposes)
  Future<void> clearAllData() async {
    final db = await database;
    await db.transaction((txn) async {
      await txn.delete(AppConstants.weightEntriesTable);
      await txn.delete(AppConstants.bloodPressureEntriesTable);
      await txn.delete(AppConstants.moodEntriesTable);
    });
  }

  /// Close the database connection
  Future<void> close() async {
    final db = _database;
    if (db != null) {
      await db.close();
      _database = null;
    }
  }

  /// Delete the database file (for testing)
  Future<void> deleteDatabase() async {
    final dbPath = await getDatabasesPath();
    final path = join(dbPath, AppConstants.databaseName);
    await databaseFactory.deleteDatabase(path);
    _database = null;
  }

  /// Check if a table exists in the database
  Future<bool> tableExists(String tableName) async {
    try {
      final db = await database;
      final result = await db.rawQuery(
        "SELECT name FROM sqlite_master WHERE type='table' AND name=?",
        [tableName],
      );
      return result.isNotEmpty;
    } catch (error) {
      return false;
    }
  }

  /// Delete entries before a specific date
  Future<void> deleteEntriesBeforeDate(DateTime date) async {
    final db = await database;
    final timestamp = date.toIso8601String();
    
    await db.delete(AppConstants.weightEntriesTable, where: 'timestamp < ?', whereArgs: [timestamp]);
    await db.delete(AppConstants.bloodPressureEntriesTable, where: 'timestamp < ?', whereArgs: [timestamp]);
    await db.delete(AppConstants.moodEntriesTable, where: 'timestamp < ?', whereArgs: [timestamp]);
  }

  /// Delete entries after a specific date
  Future<void> deleteEntriesAfterDate(DateTime date) async {
    final db = await database;
    final timestamp = date.toIso8601String();
    
    await db.delete(AppConstants.weightEntriesTable, where: 'timestamp > ?', whereArgs: [timestamp]);
    await db.delete(AppConstants.bloodPressureEntriesTable, where: 'timestamp > ?', whereArgs: [timestamp]);
    await db.delete(AppConstants.moodEntriesTable, where: 'timestamp > ?', whereArgs: [timestamp]);
  }
}
