// SPDX-FileCopyrightText: © 2025 Nøkken.io <nokken.io@proton.me>
// SPDX-License-Identifier: AGPL-3.0
//
// bloodwork_data_generator.dart
//
import 'dart:math' show Random;
import 'package:flutter/material.dart';
import 'package:nokken/src/core/services/database/database_service.dart';
import 'package:nokken/src/features/bloodwork_tracker/models/bloodwork.dart';

/// A tool class to generate historical bloodwork data
class BloodworkDataGenerator {
  final DatabaseService databaseService;

  BloodworkDataGenerator(this.databaseService);

  /// Generate bloodwork data, including:
  /// - Bloodwork labs every 70-110 days
  /// - 2-4 doctor appointments throughout the year
  /// - 1 surgery appointment
  Future<void> generateBloodworkData() async {
    final random = Random();

    // Start date: 364 days ago from today
    final endDate = DateTime.now();
    final startDate = endDate.subtract(const Duration(days: 364));

    debugPrint(
        'Generating bloodwork data from ${startDate.toIso8601String()} to ${endDate.toIso8601String()}');

    // Generate bloodwork labs
    await _generateBloodworkLabs(startDate, endDate, random);

    // Generate doctor appointments
    await _generateDoctorAppointments(startDate, endDate, random);

    // Generate a surgery appointment
    await _generateSurgeryAppointment(startDate, endDate, random);

    debugPrint('Successfully generated bloodwork data.');
  }

  /// Generate bloodwork labs every 70-110 days
  Future<void> _generateBloodworkLabs(
      DateTime startDate, DateTime endDate, Random random) async {
    // Start a bit before the actual start date to ensure we have consistent coverage
    DateTime currentDate = startDate.subtract(const Duration(days: 30));
    int labCount = 0;

    while (currentDate.isBefore(endDate)) {
      // Add a random interval between 70-110 days
      final interval = random.nextInt(41) + 70; // 70-110 days
      currentDate = currentDate.add(Duration(days: interval));

      // Only add if it's within our target range
      if (currentDate.isAfter(startDate) && currentDate.isBefore(endDate)) {
        // Generate a bloodwork entry
        final bloodwork = _generateBloodworkEntry(currentDate, random);
        await databaseService.insertBloodwork(bloodwork);
        labCount++;
      }
    }

    debugPrint('Generated $labCount bloodwork lab records');
  }

  /// Generate 2-4 doctor appointments throughout the year
  Future<void> _generateDoctorAppointments(
      DateTime startDate, DateTime endDate, Random random) async {
    // Decide how many appointments (2-4)
    final appointmentCount = random.nextInt(3) + 2; // 2-4
    debugPrint('Generating $appointmentCount doctor appointments');

    // Generate appointment dates (evenly spaced with some randomness)
    final yearInDays = 364;
    final appointmentDates = <DateTime>[];

    for (int i = 0; i < appointmentCount; i++) {
      // Calculate roughly evenly spaced dates with some randomness
      final targetDay = ((i + 1) * yearInDays / (appointmentCount + 1)).round();
      final randomOffset = random.nextInt(21) - 10; // +/- 10 days
      final dayOffset = targetDay + randomOffset;

      final appointmentDate = startDate.add(Duration(days: dayOffset));
      appointmentDates.add(appointmentDate);
    }

    // Sort dates and ensure no duplicates
    appointmentDates.sort((a, b) => a.compareTo(b));
    final uniqueDates = <DateTime>[];
    DateTime? lastDate;

    for (final date in appointmentDates) {
      if (lastDate == null || date.difference(lastDate).inDays > 30) {
        // Ensure at least 30 days between appointments
        uniqueDates.add(date);
        lastDate = date;
      }
    }

    // Create and insert appointment records
    for (final date in uniqueDates) {
      final appointment = _generateAppointmentEntry(date, random);
      await databaseService.insertBloodwork(appointment);
    }

    debugPrint('Generated ${uniqueDates.length} doctor appointment records');
  }

  /// Generate a surgery appointment within the year
  Future<void> _generateSurgeryAppointment(
      DateTime startDate, DateTime endDate, Random random) async {
    // Place surgery in the middle third of the year (not too recent, not too far back)
    final yearInDays = 364;
    final targetDayStart = yearInDays ~/ 3;
    final targetDayEnd = (yearInDays * 2) ~/ 3;

    final dayOffset =
        random.nextInt(targetDayEnd - targetDayStart) + targetDayStart;
    final surgeryDate = startDate.add(Duration(days: dayOffset));

    final surgery = _generateSurgeryEntry(surgeryDate, random);
    await databaseService.insertBloodwork(surgery);

    debugPrint('Generated surgery record for ${surgeryDate.toIso8601String()}');
  }

  /// Generate a bloodwork lab entry
  Bloodwork _generateBloodworkEntry(DateTime date, Random random) {
    // Always include the four required hormones
    final hormoneReadings = <HormoneReading>[
      _generateHormoneReading('Estradiol', random),
      _generateHormoneReading('Testosterone', random),
      _generateHormoneReading('Progesterone', random),
      _generateHormoneReading('Prolactin', random),
    ];

    // Add 0-6 additional random hormones
    final additionalCount = random.nextInt(7); // 0-6
    final allHormoneTypes = HormoneTypes.getHormoneTypes();
    final requiredTypes = {
      'Estradiol',
      'Testosterone',
      'Progesterone',
      'Prolactin'
    };
    final availableTypes =
        allHormoneTypes.where((type) => !requiredTypes.contains(type)).toList();

    // Shuffle and take random selection
    availableTypes.shuffle(random);
    for (int i = 0; i < additionalCount && i < availableTypes.length; i++) {
      hormoneReadings.add(_generateHormoneReading(availableTypes[i], random));
    }

    // Random time
    final timeHour = random.nextInt(8) + 8; // Between a8am and 4pm
    final timeMinute = random.nextInt(4) * 15; // 0, 15, 30, or 45 minutes
    final dateTime = DateTime(
      date.year,
      date.month,
      date.day,
      timeHour,
      timeMinute,
    );

    // Random location
    final locations = [
      'Main Hospital Lab',
      'Downtown Clinic',
      'Westside Medical Center',
      'University Health Services',
      'Eastside Lab',
    ];
    final location = locations[random.nextInt(locations.length)];

    // Random doctor
    final doctors = [
      'Dr. Martinez',
      'Dr. Johnson',
      'Dr. Williams',
      'Dr. Chen',
      'Dr. Taylor',
    ];
    final doctor = doctors[random.nextInt(doctors.length)];

    // Random notes
    final notesOptions = [
      'Routine bloodwork',
      'Follow-up lab work',
      'Quarterly hormone check',
      'Regular monitoring',
      'Medication adjustment labs',
      null, // Sometimes no notes
    ];
    final notes = notesOptions[random.nextInt(notesOptions.length)];

    return Bloodwork(
      date: dateTime,
      appointmentType: AppointmentType.bloodwork,
      hormoneReadings: hormoneReadings,
      location: location,
      doctor: doctor,
      notes: notes,
    );
  }

  /// Generate a doctor appointment entry
  Bloodwork _generateAppointmentEntry(DateTime date, Random random) {
    // Random time
    final timeHour = random.nextInt(8) + 9; // Between 9am and 5pm
    final timeMinute = random.nextInt(4) * 15; // 0, 15, 30, or 45 minutes
    final dateTime = DateTime(
      date.year,
      date.month,
      date.day,
      timeHour,
      timeMinute,
    );

    // Random location
    final locations = [
      'Primary Care Clinic',
      'Endocrinology Department',
      'Specialty Clinic',
      'Medical Center',
      'Healthcare Associates',
    ];
    final location = locations[random.nextInt(locations.length)];

    // Random doctor
    final doctors = [
      'Dr. Martinez',
      'Dr. Johnson',
      'Dr. Williams',
      'Dr. Chen',
      'Dr. Taylor',
    ];
    final doctor = doctors[random.nextInt(doctors.length)];

    // Random notes
    final notesOptions = [
      'Regular checkup',
      'Follow-up appointment',
      'Medication review',
      'Consultation',
      'Care planning',
      'Treatment discussion',
      null, // Sometimes no notes
    ];
    final notes = notesOptions[random.nextInt(notesOptions.length)];

    return Bloodwork(
      date: dateTime,
      appointmentType: AppointmentType.appointment,
      hormoneReadings: [], // No hormone readings for appointments
      location: location,
      doctor: doctor,
      notes: notes,
    );
  }

  /// Generate a surgery entry
  Bloodwork _generateSurgeryEntry(DateTime date, Random random) {
    // Random time (surgeries often early)
    final timeHour = random.nextInt(5) + 7; // Between 7am and 12pm
    final timeMinute = random.nextInt(4) * 15; // 0, 15, 30, or 45 minutes
    final dateTime = DateTime(
      date.year,
      date.month,
      date.day,
      timeHour,
      timeMinute,
    );

    // Random location
    final locations = [
      'University Hospital',
      'Memorial Surgery Center',
      'Medical Center',
      'Surgical Institute',
      'Regional Hospital',
    ];
    final location = locations[random.nextInt(locations.length)];

    // Random doctor
    final doctors = [
      'Dr. Garcia',
      'Dr. Smith',
      'Dr. Patel',
      'Dr. Wilson',
      'Dr. Lee',
    ];
    final doctor = doctors[random.nextInt(doctors.length)];

    // Random notes about the surgery
    final notesOptions = [
      'Scheduled procedure',
      'Outpatient surgery',
      'Routine operation',
      'Elective procedure',
      'Standard intervention',
    ];
    final notes = notesOptions[random.nextInt(notesOptions.length)];

    return Bloodwork(
      date: dateTime,
      appointmentType: AppointmentType.surgery,
      hormoneReadings: [], // No hormone readings for surgery
      location: location,
      doctor: doctor,
      notes: notes,
    );
  }

  /// Generate a random hormone reading
  HormoneReading _generateHormoneReading(String hormoneName, Random random) {
    // Get the default unit for this hormone
    final unit = HormoneTypes.getDefaultUnit(hormoneName);

    // Generate a reasonable value based on the hormone type
    double value;

    switch (hormoneName) {
      case 'Estradiol':
        // Typical range: 30-400 pg/mL
        value = 30.0 + random.nextDouble() * 370.0;
        break;
      case 'Testosterone':
        // Typical range: 15-70 ng/dL for women, 300-1000 ng/dL for men
        value = 15.0 + random.nextDouble() * 500.0;
        break;
      case 'Progesterone':
        // Typical range: 0.1-25 ng/mL
        value = 0.1 + random.nextDouble() * 24.9;
        break;
      case 'Prolactin':
        // Typical range: 3-30 ng/mL
        value = 3.0 + random.nextDouble() * 27.0;
        break;
      case 'FSH':
        // Typical range: 4-25 mIU/mL
        value = 4.0 + random.nextDouble() * 21.0;
        break;
      case 'LH':
        // Typical range: 2-20 mIU/mL
        value = 2.0 + random.nextDouble() * 18.0;
        break;
      case 'TSH':
        // Typical range: 0.4-4.0 μIU/mL
        value = 0.4 + random.nextDouble() * 3.6;
        break;
      case 'Free T3':
        // Typical range: 2.3-4.2 pg/mL
        value = 2.3 + random.nextDouble() * 1.9;
        break;
      case 'Free T4':
        // Typical range: 0.8-1.8 ng/dL
        value = 0.8 + random.nextDouble() * 1.0;
        break;
      case 'SHBG':
        // Typical range: 20-130 nmol/L
        value = 20.0 + random.nextDouble() * 110.0;
        break;
      case 'DHT':
        // Typical range: 24-65 ng/dL
        value = 24.0 + random.nextDouble() * 41.0;
        break;
      default:
        // Default range for other hormones
        value = 10.0 + random.nextDouble() * 90.0;
    }

    // Round to 1 decimal place
    value = (value * 10).round() / 10;

    return HormoneReading(
      name: hormoneName,
      value: value,
      unit: unit,
    );
  }
}
