// SPDX-FileCopyrightText: © 2025 Nøkken.io <nokken.io@proton.me>
// SPDX-License-Identifier: AGPL-3.0
//
//  bloodwork_graph_screen.dart
//  Screen that displays hormone level graphs over time
//
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:nokken/src/features/bloodwork_tracker/models/bloodwork.dart';
import 'package:nokken/src/features/bloodwork_tracker/providers/bloodwork_state.dart';
import 'package:nokken/src/features/bloodwork_tracker/screens/hormone_search_bar.dart';
import 'package:nokken/src/core/ui/theme/app_theme.dart';
import 'package:nokken/src/core/ui/theme/app_icons.dart';
import 'package:nokken/src/core/ui/theme/app_colors.dart';
import 'package:nokken/src/core/ui/theme/app_text_styles.dart';
import 'package:nokken/src/core/ui/widgets/shared_widgets.dart';
import 'package:nokken/src/core/ui/widgets/timeframe_selector.dart';
import 'package:nokken/src/core/ui/widgets/dialog_service.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:intl/intl.dart';

class BloodworkGraphScreen extends ConsumerStatefulWidget {
  final String? selectedHormone;

  const BloodworkGraphScreen({
    super.key,
    this.selectedHormone,
  });

  @override
  ConsumerState<BloodworkGraphScreen> createState() =>
      _BloodworkGraphScreenState();
}

class _BloodworkGraphScreenState extends ConsumerState<BloodworkGraphScreen>
    with TickerProviderStateMixin {
  // Changed from SingleTickerProviderStateMixin
  late TabController _tabController;

  // Time range filter
  String _selectedTimeFrame = 'All Time';

  // Search filter
  String _searchQuery = '';

  // Available hormone types
  late List<String> _hormoneTypes;
  late List<String> _filteredHormoneTypes;
  late String _selectedHormone;

  // Scroll controller for tab bar
  final ScrollController _tabScrollController = ScrollController();

  // Recommended ranges (female reference ranges)
  // todo: should be own file
  final Map<String, ({double min, double max, String unit})>
      _recommendedRanges = {
    'Estradiol': (min: 30, max: 400, unit: 'pg/mL'),
    'Testosterone': (min: 15, max: 70, unit: 'ng/dL'),
    'Progesterone': (min: 0.1, max: 25, unit: 'ng/mL'),
    'FSH': (min: 4, max: 13, unit: 'mIU/mL'),
    'LH': (min: 1, max: 20, unit: 'mIU/mL'),
    'Prolactin': (min: 4, max: 30, unit: 'ng/mL'),
    'DHEA-S': (min: 35, max: 430, unit: 'μg/dL'),
    'Cortisol': (min: 5, max: 25, unit: 'μg/dL'),
    'TSH': (min: 0.4, max: 4.0, unit: 'μIU/mL'),
    'Free T4': (min: 0.8, max: 1.8, unit: 'ng/dL'),
  };

  @override
  void initState() {
    super.initState();

    // Initialize with the selected hormone or default to first available
    _selectedHormone = widget.selectedHormone ?? '';

    // Extract hormone types from data
    final bloodworkRecords = ref.read(bloodworkTypeRecordsProvider);
    _hormoneTypes = _extractHormoneTypes(bloodworkRecords);
    _filteredHormoneTypes = _hormoneTypes; // Initialize filtered list

    // If no selected hormone or selected hormone not in the list, use first one
    if (_selectedHormone.isEmpty || !_hormoneTypes.contains(_selectedHormone)) {
      _selectedHormone = _hormoneTypes.isNotEmpty ? _hormoneTypes[0] : '';
    }

    // Initialize tab controller with the number of available hormone types
    _tabController = TabController(
      length: _hormoneTypes.length,
      vsync: this,
      initialIndex: _selectedHormone.isNotEmpty
          ? _hormoneTypes.indexOf(_selectedHormone)
          : 0,
    );

    // Listen to tab changes
    _tabController.addListener(_onTabChanged);
  }

  // Tab change listener as a separate method so we can remove it when needed
  void _onTabChanged() {
    if (_tabController.indexIsChanging) return;
    if (_filteredHormoneTypes.isNotEmpty) {
      setState(() {
        _selectedHormone = _filteredHormoneTypes[_tabController.index];
      });
    }
  }

  /// Extract all available hormone types from records
  List<String> _extractHormoneTypes(List<Bloodwork> records) {
    // Set to store unique hormone types
    final Set<String> hormoneSet = {};

    // Extract from hormone readings
    for (final record in records) {
      for (final reading in record.hormoneReadings) {
        hormoneSet.add(reading.name);
      }
    }

    // Sort alphabetically
    final hormoneList = hormoneSet.toList()..sort();
    return hormoneList;
  }

  /// Filter hormone types based on search query
  void _filterHormoneTypes(String query) {
    setState(() {
      _searchQuery = query.toLowerCase();

      if (_searchQuery.isEmpty) {
        _filteredHormoneTypes = _hormoneTypes;
      } else {
        _filteredHormoneTypes = _hormoneTypes
            .where((hormone) => hormone.toLowerCase().contains(_searchQuery))
            .toList();
      }

      // Update selected hormone if needed
      if (_filteredHormoneTypes.isNotEmpty) {
        if (!_filteredHormoneTypes.contains(_selectedHormone)) {
          _selectedHormone = _filteredHormoneTypes[0];
        }
      } else {
        _selectedHormone = '';
      }

      // Update tab controller
      _updateTabController();
    });
  }

  /// Update tab controller when filtered list changes
  void _updateTabController() {
    // Remove listener before disposing
    _tabController.removeListener(_onTabChanged);

    // Find the appropriate index for the selected hormone
    final int newIndex = _selectedHormone.isNotEmpty &&
            _filteredHormoneTypes.contains(_selectedHormone)
        ? _filteredHormoneTypes.indexOf(_selectedHormone)
        : 0;

    // Dispose the old controller before creating a new one
    _tabController.dispose();

    // Create a new controller
    _tabController = TabController(
      length: _filteredHormoneTypes.length,
      vsync: this,
      initialIndex: _filteredHormoneTypes.isEmpty ? 0 : newIndex,
    );

    // Reattach the listener
    _tabController.addListener(_onTabChanged);
  }

  @override
  void dispose() {
    _tabController.dispose();
    _tabScrollController.dispose();
    super.dispose();
  }

  List<Bloodwork> _getFilteredData(List<Bloodwork> allRecords) {
    if (_selectedTimeFrame == 'All Time') {
      return allRecords;
    }

    final now = DateTime.now();
    DateTime cutoffDate;

    switch (_selectedTimeFrame) {
      case '3 Months':
        cutoffDate = DateTime(now.year, now.month - 3, now.day);
        break;
      case '6 Months':
        cutoffDate = DateTime(now.year, now.month - 6, now.day);
        break;
      case '1 Year':
        cutoffDate = DateTime(now.year - 1, now.month, now.day);
        break;
      default:
        return allRecords;
    }

    return allRecords
        .where((record) => record.date.isAfter(cutoffDate))
        .toList();
  }

  @override
  Widget build(BuildContext context) {
    // Use the bloodwork-type only provider for hormone graphs
    final bloodworkRecords = ref.watch(bloodworkTypeRecordsProvider);
    final isLoading = ref.watch(bloodworkLoadingProvider);
    final error = ref.watch(bloodworkErrorProvider);

    // Check if we need to update the hormone types and tabs
    final newHormoneTypes = _extractHormoneTypes(bloodworkRecords);
    if (newHormoneTypes.length != _hormoneTypes.length ||
        !newHormoneTypes.every((e) => _hormoneTypes.contains(e))) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        setState(() {
          _hormoneTypes = newHormoneTypes;
          // Apply current search filter
          _filterHormoneTypes(_searchQuery);
        });
      });
    }

    // Get filtered data based on time range
    final filteredRecords = _getFilteredData(bloodworkRecords);

    // Sort the records chronologically (oldest first) for chart display
    final chronologicalRecords = [...filteredRecords]
      ..sort((a, b) => a.date.compareTo(b.date));

    return Scaffold(
      appBar: AppBar(
        title: Text(
            _selectedHormone.isNotEmpty ? _selectedHormone : 'Blood Levels'),
        bottom: PreferredSize(
          preferredSize:
              const Size.fromHeight(96), // Increased height for search bar
          child: Column(
            children: [
              // Search bar
              Padding(
                padding: const EdgeInsets.symmetric(horizontal: 16.0),
                child: HormoneSearchBar(
                  hintText: 'Search hormones...',
                  onSearch: _filterHormoneTypes,
                  inAppBar: true,
                ),
              ),
              // Tabs
              Container(
                alignment: Alignment.centerLeft,
                child: TabBar(
                  controller: _tabController,
                  isScrollable: true,
                  tabAlignment: TabAlignment.start,
                  tabs: _filteredHormoneTypes
                      .map((type) => Tab(text: type))
                      .toList(),
                ),
              ),
            ],
          ),
        ),
        actions: [
          // Timeframe filter dropdown
          TimeframeSelector.dropdownButton(
            context: context,
            selectedTimeframe: _selectedTimeFrame,
            onTimeframeSelected: (value) {
              setState(() {
                _selectedTimeFrame = value;
              });
            },
          ),
        ],
      ),
      body: error != null
          ? Center(
              child: Text(
                'Error: $error',
                style: TextStyle(color: AppColors.error),
              ),
            )
          : isLoading
              ? const Center(child: CircularProgressIndicator())
              : _selectedHormone.isEmpty ||
                      chronologicalRecords.isEmpty ||
                      _filteredHormoneTypes.isEmpty
                  ? Center(
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          Icon(
                            AppIcons.getOutlined('bloodwork'),
                            size: 64,
                            color: AppColors.bloodwork,
                          ),
                          SharedWidgets.verticalSpace(AppTheme.doubleSpacing),
                          const Text(
                            'No bloodwork data available for the selected time range',
                            textAlign: TextAlign.center,
                          ),
                          SharedWidgets.verticalSpace(),
                          const Text(
                            'Only lab appointments with hormone levels are shown in graphs',
                            textAlign: TextAlign.center,
                            style: TextStyle(
                              fontSize: 14,
                              fontStyle: FontStyle.italic,
                            ),
                          ),
                        ],
                      ),
                    )
                  : TabBarView(
                      controller: _tabController,
                      children: _filteredHormoneTypes
                          .map(
                            (hormoneType) => _buildChartContainer(
                              chronologicalRecords,
                              hormoneType,
                              _getHormoneColor(hormoneType),
                              (record) => _getHormoneValue(record, hormoneType),
                              _getHormoneUnit(
                                  chronologicalRecords, hormoneType),
                            ),
                          )
                          .toList(),
                    ),
    );
  }

  // Generate colors for hormones using hashes
  Color _getHormoneColor(String hormoneType) {
    final hash = hormoneType.hashCode;
    return Color.fromARGB(
      255,
      (hash & 0xFF),
      ((hash >> 8) & 0xFF),
      ((hash >> 16) & 0xFF),
    );
  }

  /// Get the hormone value from a record
  double? _getHormoneValue(Bloodwork record, String hormoneType) {
    // First check in hormone readings
    for (final reading in record.hormoneReadings) {
      if (reading.name == hormoneType) {
        return reading.value;
      }
    }

    return null;
  }

  /// Get the unit for this hormone type
  String _getHormoneUnit(List<Bloodwork> records, String hormoneType) {
    // First try to find it in the data
    for (final record in records) {
      for (final reading in record.hormoneReadings) {
        if (reading.name == hormoneType) {
          return reading.unit;
        }
      }
    }

    // Try to get from recommended ranges
    if (_recommendedRanges.containsKey(hormoneType)) {
      return _recommendedRanges[hormoneType]!.unit;
    }

    // Fall back to default units
    return HormoneTypes.getDefaultUnit(hormoneType);
  }

  Widget _buildChartContainer(
    List<Bloodwork> records,
    String title,
    Color lineColor,
    double? Function(Bloodwork) valueGetter,
    String unit,
  ) {
    // Filter out records with null values for this hormone
    final validRecords =
        records.where((record) => valueGetter(record) != null).toList();

    if (validRecords.isEmpty) {
      return Center(
        child: Text('No data available for $title'),
      );
    }

    return Padding(
      padding: AppTheme.standardCardPadding,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            '$title ($unit)',
            style: AppTextStyles.titleMedium,
          ),
          SharedWidgets.verticalSpace(),
          Text(
            'Time range: $_selectedTimeFrame',
            style: AppTextStyles.bodySmall,
          ),
          SharedWidgets.verticalSpace(AppTheme.tripleSpacing),
          Expanded(
            child: _buildLineChart(
                validRecords, lineColor, valueGetter, unit, title),
          ),
        ],
      ),
    );
  }

  Widget _buildLineChart(
    List<Bloodwork> records,
    Color lineColor,
    double? Function(Bloodwork) valueGetter,
    String unit,
    String hormoneType,
  ) {
    // Get all values to calculate min and max for Y axis
    final values = records
        .map((record) => valueGetter(record))
        .where((value) => value != null)
        .map((value) => value!)
        .toList();

    if (values.isEmpty) {
      return const Center(child: Text('No data available'));
    }

    // Calculate min/max Y values
    double minY =
        (values.reduce((a, b) => a < b ? a : b) * 0.8).floorToDouble();
    double maxY = (values.reduce((a, b) => a > b ? a : b) * 1.2).ceilToDouble();

    // Adjust based on recommended ranges if available
    if (_recommendedRanges.containsKey(hormoneType)) {
      final range = _recommendedRanges[hormoneType]!;
      // Ensure the range is visible on the chart
      minY = minY < range.min ? minY : (range.min * 0.8).floorToDouble();
      maxY = maxY > range.max ? maxY : (range.max * 1.2).ceilToDouble();
    }

    return LineChart(
      LineChartData(
        gridData: const FlGridData(
          show: true,
          drawVerticalLine: true,
          drawHorizontalLine: true,
        ),
        titlesData: FlTitlesData(
          bottomTitles: AxisTitles(
            sideTitles: SideTitles(
              showTitles: true,
              reservedSize: 30,
              getTitlesWidget: (value, meta) {
                final int index = value.toInt();

                // Only show titles for actual data points
                if (index >= 0 && index < records.length) {
                  final date = records[index].date;

                  // For single data point, always show the date
                  if (records.length == 1) {
                    final dateFormat = DateFormat('MM/dd/yy');
                    return Padding(
                      padding: const EdgeInsets.only(top: 8.0),
                      child: Text(
                        dateFormat.format(date),
                        style: AppTextStyles.labelSmall,
                      ),
                    );
                  }

                  // For multiple data points, ensure even distribution
                  if (records.length < 8) {
                    // For small datasets, show all dates
                    final dateFormat = _dateFormatSpansMultipleYears(records)
                        ? DateFormat('MM/dd/yy')
                        : DateFormat('MM/dd');

                    return Padding(
                      padding: const EdgeInsets.only(top: 8.0),
                      child: Text(
                        dateFormat.format(date),
                        style: AppTextStyles.labelSmall,
                      ),
                    );
                  } else {
                    // For larger datasets, show dates at fixed intervals
                    // Ensure first and last point always show, plus some evenly spaced ones
                    int interval = (records.length / 5).ceil();
                    if (index == 0 ||
                        index == records.length - 1 ||
                        index % interval == 0) {
                      final dateFormat = _dateFormatSpansMultipleYears(records)
                          ? DateFormat('MM/dd/yy')
                          : DateFormat('MM/dd');

                      return Padding(
                        padding: const EdgeInsets.only(top: 8.0),
                        child: Text(
                          dateFormat.format(date),
                          style: AppTextStyles.labelSmall,
                        ),
                      );
                    }
                  }
                }
                return const SizedBox();
              },
            ),
          ),
          leftTitles: AxisTitles(
            sideTitles: SideTitles(
              showTitles: true,
              reservedSize: 50,
              getTitlesWidget: (value, meta) {
                return Padding(
                  padding: const EdgeInsets.only(right: 8.0),
                  child: Text(
                    value.toStringAsFixed(1),
                    style: AppTextStyles.labelSmall,
                  ),
                );
              },
            ),
          ),
          topTitles:
              const AxisTitles(sideTitles: SideTitles(showTitles: false)),
          rightTitles:
              const AxisTitles(sideTitles: SideTitles(showTitles: false)),
        ),
        borderData: FlBorderData(
          show: true,
          border: Border.all(color: AppColors.outline.withAlpha(150)),
        ),
        minX: 0,
        maxX: records.length - 1.0,
        minY: minY,
        maxY: maxY,
        lineBarsData: [
          LineChartBarData(
            spots: _createSpots(records, valueGetter),
            isCurved: true,
            color: lineColor,
            barWidth: 3,
            isStrokeCapRound: true,
            dotData: const FlDotData(show: true),
            belowBarData: BarAreaData(
              show: true,
              color: lineColor.withAlpha(40),
            ),
          ),
          // Add recommended range overlay if available
          if (_recommendedRanges.containsKey(hormoneType))
            ..._createRecommendedRangeOverlay(records, hormoneType),
        ],
        lineTouchData: LineTouchData(
          enabled: true,
          // Disable built-in hover behavior
          handleBuiltInTouches: false,
          touchTooltipData: LineTouchTooltipData(
            // Make tooltip invisible since we'll show a custom dialog
            tooltipBgColor: Colors.transparent,
            tooltipPadding: EdgeInsets.zero,
            tooltipMargin: 0,
            getTooltipItems: (List<LineBarSpot> touchedBarSpots) {
              // Return null for all spots to prevent the default tooltip
              return touchedBarSpots.map((_) => null).toList();
            },
          ),

          // Handle tap events to show a custom dialog
          touchCallback:
              (FlTouchEvent event, LineTouchResponse? touchResponse) {
            // Only respond to tap events
            if (event is! FlTapUpEvent ||
                touchResponse == null ||
                touchResponse.lineBarSpots == null ||
                touchResponse.lineBarSpots!.isEmpty) {
              return;
            }

            final spot = touchResponse.lineBarSpots!.first;
            if (spot.barIndex == 0) {
              // Only respond to main data line
              final index = spot.x.toInt();
              if (index >= 0 && index < records.length) {
                final record = records[index];
                final value = valueGetter(record)!;

                DialogService.showBloodworkDetailDialog(
                  context: context,
                  hormoneType: hormoneType,
                  date: record.date,
                  value: value,
                  unit: unit,
                  record: record,
                );
              }
            }
          },
          // Make touch area larger for easier tapping
          touchSpotThreshold: 20,
        ),
      ),
    );
  }

  /// Create spots for the line chart
  List<FlSpot> _createSpots(
    List<Bloodwork> records,
    double? Function(Bloodwork) valueGetter,
  ) {
    final spots = <FlSpot>[];

    for (int i = 0; i < records.length; i++) {
      final value = valueGetter(records[i]);
      if (value != null) {
        spots.add(FlSpot(i.toDouble(), value));
      }
    }

    // Create a horizontal line if there's only one data point
    if (spots.length == 1) {
      // Add a second point with the same Y value but at X position 1
      final spot = spots.first;
      spots.add(FlSpot(1.0, spot.y));
      // Set the first point to X position 0
      spots[0] = FlSpot(0.0, spot.y);
    }

    return spots;
  }

  /// Create overlays for recommended ranges
  List<LineChartBarData> _createRecommendedRangeOverlay(
      List<Bloodwork> records, String hormoneType) {
    if (!_recommendedRanges.containsKey(hormoneType)) {
      return [];
    }

    final range = _recommendedRanges[hormoneType]!;
    final minY = range.min;
    final maxY = range.max;

    return [
      // Minimum line
      LineChartBarData(
        spots: [
          FlSpot(0, minY),
          FlSpot(records.length - 1, minY),
        ],
        isCurved: false,
        color: AppTheme.grey.withAlpha(160),
        barWidth: 1,
        isStrokeCapRound: false,
        dotData: const FlDotData(show: false),
        dashArray: [5, 5], // Dashed line
        // Disable touch interaction for reference lines
        show: true,
      ),
      // Maximum line
      LineChartBarData(
        spots: [
          FlSpot(0, maxY),
          FlSpot(records.length - 1, maxY),
        ],
        isCurved: false,
        color: AppTheme.grey.withAlpha(160),
        barWidth: 1,
        isStrokeCapRound: false,
        dotData: const FlDotData(show: false),
        dashArray: [5, 5], // Dashed line
        // Disable touch interaction for reference lines
        show: true,
      ),
      // Filled area between min and max
      LineChartBarData(
        spots: [
          FlSpot(0, minY),
          FlSpot(records.length - 1, minY),
        ],
        isCurved: false,
        color: Colors.transparent,
        barWidth: 0,
        isStrokeCapRound: false,
        dotData: const FlDotData(show: false),
        belowBarData: BarAreaData(
          show: false,
        ),
        aboveBarData: BarAreaData(
          show: true,
          color: Colors.green.withAlpha(20),
          cutOffY: maxY,
          applyCutOffY: true,
        ),
        // Disable touch interaction for reference lines
        show: true,
      ),
    ];
  }

  /// Check if the dataset spans multiple years
  bool _dateFormatSpansMultipleYears(List<Bloodwork> records) {
    if (records.length < 2) return false;

    final firstYear = records.first.date.year;
    return records.any((record) => record.date.year != firstYear);
  }
}
