// SPDX-FileCopyrightText: © 2025 Nøkken.io <nokken.io@proton.me>
// SPDX-License-Identifier: AGPL-3.0
//
//  bloodwork.dart
//  Model for bloodwork lab results
//
import 'package:uuid/uuid.dart';

/// Custom exception for bloodwork-related errors
class BloodworkException implements Exception {
  final String message;
  BloodworkException(this.message);

  @override
  String toString() => 'BloodworkException: $message';
}

/// Types of appointments supported by the bloodwork tracker
enum AppointmentType { bloodwork, appointment, surgery }

/// Represents a single hormone reading with name, value and unit
class HormoneReading {
  final String name;
  final double value;
  final String unit;
  final DateTime? date;
  final double? minValue;
  final double? maxValue;

  HormoneReading({
    required this.name,
    required this.value,
    required this.unit,
    this.date,
    this.minValue,
    this.maxValue,
  });

  /// Convert to JSON format for database storage
  Map<String, dynamic> toJson() {
    return {
      'name': name,
      'value': value,
      'unit': unit,
      'date': date?.toIso8601String(),
      'minValue': minValue,
      'maxValue': maxValue,
    };
  }

  factory HormoneReading.fromJson(Map<String, dynamic> json) {
    return HormoneReading(
      name: json['name'] as String,
      value: (json['value'] as num).toDouble(),
      unit: json['unit'] as String,
      date:
          json['date'] != null ? DateTime.parse(json['date'] as String) : null,
      minValue: json['minValue'] != null
          ? (json['minValue'] as num).toDouble()
          : null,
      maxValue: json['maxValue'] != null
          ? (json['maxValue'] as num).toDouble()
          : null,
    );
  }

  /// Create a copy with updated fields
  HormoneReading copyWith({
    String? name,
    double? value,
    String? unit,
    DateTime? date,
    double? minValue,
    double? maxValue,
  }) {
    return HormoneReading(
      name: name ?? this.name,
      value: value ?? this.value,
      unit: unit ?? this.unit,
      date: date ?? this.date,
      minValue: minValue ?? this.minValue,
      maxValue: maxValue ?? this.maxValue,
    );
  }
}

/// Predefined hormone types with their default units
class HormoneTypes {
  static const Map<String, String> defaultUnits = {
    // Sex Hormones
    'Estradiol': 'pg/mL',
    'Free Estradiol': 'pg/mL',
    'Estrone': 'pg/mL',
    'Estriol': 'pg/mL',
    'Testosterone': 'ng/dL',
    'Free Testosterone': 'ng/dL',
    'Bioavailable Testosterone': 'ng/dL',
    'DHT': 'ng/dL',
    'Progesterone': 'ng/mL',
    'SHBG': 'nmol/L',
    'Free Androgen Index': 'ratio',
    'Androstenedione': 'ng/dL',
    'DHEA': 'μg/dL',
    'DHEA-S': 'μg/dL',
    '17-OH Progesterone': 'ng/mL',

    // Pituitary Hormones
    'FSH': 'mIU/mL',
    'LH': 'mIU/mL',
    'Prolactin': 'ng/mL',
    'Growth Hormone': 'ng/mL',
    'IGF-1': 'ng/mL',

    // Thyroid Hormones
    'TSH': 'μIU/mL',
    'Free T3': 'pg/mL',
    'Total T3': 'ng/dL',
    'Free T4': 'ng/dL',
    'Total T4': 'μg/dL',
    'Reverse T3': 'ng/dL',
    'Thyroid Peroxidase Antibodies': 'IU/mL',
    'Thyroglobulin Antibodies': 'IU/mL',
    'Thyroglobulin': 'ng/mL',

    // Adrenal Hormones
    'Cortisol': 'μg/dL',
    'Cortisone': 'μg/dL',
    'Aldosterone': 'ng/dL',
    'Epinephrine': 'pg/mL',
    'Norepinephrine': 'pg/mL',

    // Metabolic Markers
    'Insulin': 'μIU/mL',
    'C-Peptide': 'ng/mL',
    'Leptin': 'ng/mL',
    'Adiponectin': 'μg/mL',

    // Vitamin D and Calcium Regulation
    'Vitamin D (25-OH)': 'ng/mL',
    'Vitamin D (1,25-OH)': 'pg/mL',
    'Parathyroid Hormone': 'pg/mL',
    'Calcitonin': 'pg/mL',

    // Other Hormones
    'Melatonin': 'pg/mL',
    'AMH': 'ng/mL',
    'Inhibin B': 'pg/mL',
    'Activin': 'pg/mL',
    'Oxytocin': 'pg/mL',
    'Vasopressin': 'pg/mL',
  };

  /// Get the default unit for a hormone type
  static String getDefaultUnit(String hormoneName) {
    return defaultUnits[hormoneName] ?? '';
  }

  /// Get list of available hormone types
  static List<String> getHormoneTypes() {
    return defaultUnits.keys.toList();
  }
}

/// Primary model for bloodwork lab test data
class Bloodwork {
  final String id;
  final DateTime date;
  final AppointmentType appointmentType;
  final List<HormoneReading> hormoneReadings;
  final String? location;
  final String? doctor;
  final String? notes;

  /// Constructor with validation
  Bloodwork({
    String? id,
    required this.date,
    this.appointmentType = AppointmentType.bloodwork,
    List<HormoneReading>? hormoneReadings,
    this.location,
    this.doctor,
    this.notes,
  })  : id = id ?? const Uuid().v4(),
        hormoneReadings = hormoneReadings ?? [] {
    _validate();
  }

  /// Validates bloodwork fields
  void _validate() {
    // Validate hormone readings
    for (final reading in hormoneReadings) {
      if (reading.value < 0) {
        throw BloodworkException('${reading.name} level cannot be negative');
      }
    }
  }

  /// Convert to JSON format for database storage
  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'date': date.toIso8601String(),
      'appointmentType': appointmentType.toString(),
      'hormoneReadings':
          hormoneReadings.map((reading) => reading.toJson()).toList(),
      'location': location?.trim(),
      'doctor': doctor?.trim(),
      'notes': notes?.trim(),
    };
  }

  /// Create a Bloodwork instance from JSON (database record)
  factory Bloodwork.fromJson(Map<String, dynamic> json) {
    try {
      // Parse appointment type from string
      AppointmentType parsedType;
      try {
        parsedType = AppointmentType.values.firstWhere(
            (e) => e.toString() == json['appointmentType'],
            orElse: () => AppointmentType.bloodwork);
      } catch (_) {
        // For backward compatibility with old records without appointmentType
        parsedType = AppointmentType.bloodwork;
      }

      // Handle the transition between old and new formats
      List<HormoneReading> readings = [];

      // First try to parse new format with hormoneReadings list
      if (json['hormoneReadings'] != null) {
        readings = (json['hormoneReadings'] as List)
            .map((reading) => HormoneReading.fromJson(reading))
            .toList();
      }

      return Bloodwork(
        id: json['id'] as String,
        date: DateTime.parse(json['date']),
        appointmentType: parsedType,
        hormoneReadings: readings,
        location: json['location'] as String?,
        doctor: json['doctor'] as String?,
        notes: json['notes'] as String?,
      );
    } catch (e) {
      throw BloodworkException('Invalid bloodwork data: $e');
    }
  }

  /// Create a copy of this bloodwork with updated fields
  Bloodwork copyWith({
    DateTime? date,
    AppointmentType? appointmentType,
    List<HormoneReading>? hormoneReadings,
    String? location,
    String? doctor,
    String? notes,
  }) {
    return Bloodwork(
      id: id,
      date: date ?? this.date,
      appointmentType: appointmentType ?? this.appointmentType,
      hormoneReadings: hormoneReadings ?? this.hormoneReadings,
      location: location ?? this.location,
      doctor: doctor ?? this.doctor,
      notes: notes ?? this.notes,
    );
  }
}
