import 'dart:convert';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';

import '../../constant/links.dart';
import '../../print.dart';
import '../functions/crud.dart';

// ══════════════════════════════════════════════════════════════
// MODEL: Represents one employee's full data for a period
// ══════════════════════════════════════════════════════════════
class EmployeeChartData {
  final String name;
  final Color color;
  final List<FlSpot> notesSpots;
  final List<FlSpot> callsSpots;

  const EmployeeChartData({
    required this.name,
    required this.color,
    required this.notesSpots,
    required this.callsSpots,
  });

  int get totalNotes => notesSpots.fold(0, (sum, s) => sum + s.y.toInt());
  int get totalCalls => callsSpots.fold(0, (sum, s) => sum + s.y.toInt());

  EmployeeChartData copyWith({
    List<FlSpot>? notesSpots,
    List<FlSpot>? callsSpots,
  }) {
    return EmployeeChartData(
      name: name,
      color: color,
      notesSpots: notesSpots ?? this.notesSpots,
      callsSpots: callsSpots ?? this.callsSpots,
    );
  }
}

// ══════════════════════════════════════════════════════════════
// MODEL: Employment activation stats per employee
// ══════════════════════════════════════════════════════════════
class EmploymentStat {
  final String name;
  final int count;
  final Color color;

  const EmploymentStat({
    required this.name,
    required this.count,
    required this.color,
  });
}

// ══════════════════════════════════════════════════════════════
// CONTROLLER
// ══════════════════════════════════════════════════════════════
class StaticController extends GetxController {
  // ─── Color Palette for Dynamic Employees ───────────────────
  static const List<Color> _employeeColors = [
    Color(0xFF00D4AA), // teal
    Color(0xFF82AAFF), // blue
    Color(0xFFFFCB6B), // amber
    Color(0xFFC792EA), // purple
    Color(0xFFFF5370), // red
    Color(0xFFC3E88D), // green
    Color(0xFFF07178), // coral
    Color(0xFF89DDFF), // cyan
  ];

  Color _colorForIndex(int i) => _employeeColors[i % _employeeColors.length];

  // ─── Date & State ───────────────────────────────────────────
  DateTime? startDate = DateTime(DateTime.now().year, DateTime.now().month, 1);
  DateTime? endDate =
      DateTime(DateTime.now().year, DateTime.now().month + 1, 0);

  DateTime? compareStartDate;
  DateTime? compareEndDate;

  bool isComparing = false;
  bool isLoading = false;

  // ─── Daily Notes State ─────────────────────────────────────
  bool isLoadingNotes = false;
  List<dynamic> dailyNotesList = [];

  // ─── Main Chart Data ───────────────────────────────────────
  List<FlSpot> chartDataPassengers = [];
  List<FlSpot> chartDataDrivers = [];
  List<FlSpot> chartDataRides = [];
  List<FlSpot> chartDataDriversMatchingNotes = [];

  List<FlSpot> chartDataPassengersCompare = [];
  List<FlSpot> chartDataDriversCompare = [];
  List<FlSpot> chartDataRidesCompare = [];
  List<FlSpot> chartDataDriversMatchingNotesCompare = [];

  // ─── 🔥 DYNAMIC Employee Data ─────────────────────────────
  // Key = employee name (from server), Value = their chart data
  Map<String, EmployeeChartData> employeeData = {};
  Map<String, EmployeeChartData> employeeDataCompare = {};

  // Set of all known employee names (union of current + compare)
  Set<String> get allEmployeeNames => {
        ...employeeData.keys,
        ...employeeDataCompare.keys,
      };

  // ─── Employment Stats ──────────────────────────────────────
  List<EmploymentStat> employmentStatsList = [];

  // ─── Totals ────────────────────────────────────────────────
  String totalMonthlyPassengers = '0';
  String totalMonthlyRides = '0';
  String totalMonthlyDrivers = '0';

  // ─── Raw Lists ─────────────────────────────────────────────
  List staticList = [];

  // ─── Color Registry (stable across rebuilds) ───────────────
  final Map<String, Color> _employeeColorRegistry = {};

  Color _getOrAssignColor(String name) {
    if (!_employeeColorRegistry.containsKey(name)) {
      _employeeColorRegistry[name] =
          _colorForIndex(_employeeColorRegistry.length);
    }
    return _employeeColorRegistry[name]!;
  }

  @override
  void onInit() {
    super.onInit();
    getAll();
  }

  // ─── Helpers ───────────────────────────────────────────────
  double get daysInPeriod {
    if (startDate == null || endDate == null) return 31;
    return endDate!.difference(startDate!).inDays + 1.0;
  }

  String get currentDateString {
    if (startDate == null || endDate == null) return "";
    return "${DateFormat('yyyy-MM-dd').format(startDate!)} : "
        "${DateFormat('yyyy-MM-dd').format(endDate!)}";
  }

  String get compareDateString {
    if (compareStartDate == null || compareEndDate == null) return "";
    return "${DateFormat('yyyy-MM-dd').format(compareStartDate!)} : "
        "${DateFormat('yyyy-MM-dd').format(compareEndDate!)}";
  }

  // ─── Date Actions ──────────────────────────────────────────
  void updateDateRange(DateTime start, DateTime end) {
    startDate = start;
    endDate = end;
    if (isComparing) _calculateCompareDates();
    getAll();
    update();
  }

  void _calculateCompareDates() {
    if (startDate == null || endDate == null) return;
    Duration duration = endDate!.difference(startDate!);
    compareEndDate = startDate!.subtract(const Duration(days: 1));
    compareStartDate = compareEndDate!.subtract(duration);
  }

  Future<void> toggleComparison() async {
    isComparing = !isComparing;
    if (isComparing) {
      _calculateCompareDates();
    } else {
      compareStartDate = null;
      compareEndDate = null;
      _clearComparisonData();
    }
    await getAll();
  }

  void _clearComparisonData() {
    chartDataPassengersCompare.clear();
    chartDataDriversCompare.clear();
    chartDataRidesCompare.clear();
    chartDataDriversMatchingNotesCompare.clear();
    employeeDataCompare.clear();
  }

  Map<String, dynamic> _getPayload(DateTime start, DateTime end) => {
        "start_date": DateFormat('yyyy-MM-dd').format(start),
        "end_date": DateFormat('yyyy-MM-dd').format(end),
        "month": start.month.toString(),
        "year": start.year.toString(),
      };

  // ─── Main Fetch ────────────────────────────────────────────
  Future<void> getAll() async {
    if (startDate == null || endDate == null) return;

    isLoading = true;
    update();

    await Future.wait([
      fetchPassengers(isCompare: false),
      fetchRides(isCompare: false),
      fetchDrivers(isCompare: false),
      fetchEmployeeDynamic(isCompare: false),
      fetchEditorCallsDynamic(isCompare: false),
      fetchEmploymentStats(),
    ]);

    if (isComparing && compareStartDate != null && compareEndDate != null) {
      await Future.wait([
        fetchPassengers(isCompare: true),
        fetchRides(isCompare: true),
        fetchDrivers(isCompare: true),
        fetchEmployeeDynamic(isCompare: true),
        fetchEditorCallsDynamic(isCompare: true),
      ]);
    }

    isLoading = false;
    update();
  }

  // ─── Spot Generator ───────────────────────────────────────
  List<FlSpot> _generateSpots(
    List<dynamic> data,
    String dateKey,
    String valueKey,
    DateTime startOfRange,
    DateTime endOfRange,
  ) {
    Map<String, double> dataMap = {
      for (var item in data)
        item[dateKey].toString():
            double.tryParse(item[valueKey].toString()) ?? 0.0
    };

    int totalDays = endOfRange.difference(startOfRange).inDays + 1;
    return List.generate(totalDays, (i) {
      final date = startOfRange.add(Duration(days: i));
      final key = DateFormat('yyyy-MM-dd').format(date);
      return FlSpot((i + 1).toDouble(), dataMap[key] ?? 0.0);
    });
  }

  /// Generates spots map keyed by employee name from a date→name→value structure
  Map<String, List<FlSpot>> _generateEmployeeSpots(
    Map<String, Map<String, double>> dateNameMap,
    DateTime start,
    DateTime end,
  ) {
    // Discover all employee names dynamically
    final Set<String> names = {};
    for (var dayData in dateNameMap.values) {
      names.addAll(dayData.keys);
    }

    int totalDays = end.difference(start).inDays + 1;
    final Map<String, List<FlSpot>> result = {};

    for (final name in names) {
      result[name] = List.generate(totalDays, (i) {
        final date = start.add(Duration(days: i));
        final dateStr = DateFormat('yyyy-MM-dd').format(date);
        final value = dateNameMap[dateStr]?[name] ?? 0.0;
        return FlSpot((i + 1).toDouble(), value);
      });
    }
    return result;
  }

  /// Parses a list of {date/day, NAME, count} records into a dateNameMap
  Map<String, Map<String, double>> _parseDateNameMap(List<dynamic> jsonData) {
    final Map<String, Map<String, double>> result = {};
    for (var item in jsonData) {
      final dateStr = (item['date'] ?? item['day']).toString();
      final name = item['NAME'].toString().toLowerCase().trim();
      final count = double.tryParse(item['count'].toString()) ?? 0.0;
      result.putIfAbsent(dateStr, () => {})[name] =
          (result[dateStr]?[name] ?? 0) + count;
    }
    return result;
  }

  // ─── Passengers ───────────────────────────────────────────
  Future<void> fetchPassengers({bool isCompare = false}) async {
    final start = isCompare ? compareStartDate! : startDate!;
    final end = isCompare ? compareEndDate! : endDate!;
    final res = await CRUD().get(
        link: AppLink.getPassengersStatic, payload: _getPayload(start, end));
    final json = jsonDecode(res);
    if (json['status'] == 'failure') return;
    final List<dynamic> data = json['message'];
    if (!isCompare && data.isNotEmpty && data[0]['totalMonthly'] != null) {
      totalMonthlyPassengers = data[0]['totalMonthly'].toString();
    }
    final spots = _generateSpots(data, 'day', 'totalPassengers', start, end);
    if (isCompare)
      chartDataPassengersCompare = spots;
    else
      chartDataPassengers = spots;
  }

  // ─── Rides ────────────────────────────────────────────────
  Future<void> fetchRides({bool isCompare = false}) async {
    final start = isCompare ? compareStartDate! : startDate!;
    final end = isCompare ? compareEndDate! : endDate!;
    final res = await CRUD()
        .get(link: AppLink.getRidesStatic, payload: _getPayload(start, end));
    final json = jsonDecode(res);
    if (json['status'] == 'failure') return;
    final List<dynamic> data = json['message'];
    if (!isCompare && data.isNotEmpty && data[0]['totalMonthly'] != null) {
      totalMonthlyRides = data[0]['totalMonthly'].toString();
    }
    final spots = _generateSpots(data, 'day', 'totalRides', start, end);
    if (isCompare)
      chartDataRidesCompare = spots;
    else
      chartDataRides = spots;
  }

  // ─── Drivers ──────────────────────────────────────────────
  Future<void> fetchDrivers({bool isCompare = false}) async {
    final start = isCompare ? compareStartDate! : startDate!;
    final end = isCompare ? compareEndDate! : endDate!;
    final res = await CRUD().get(
        link: AppLink.getdriverstotalMonthly, payload: _getPayload(start, end));
    final json = jsonDecode(res);
    if (json['status'] == 'failure') return;
    final List<dynamic> data = json['message'];
    if (!isCompare &&
        data.isNotEmpty &&
        data[0]['totalMonthlyDrivers'] != null) {
      totalMonthlyDrivers = data[0]['totalMonthlyDrivers'].toString();
      staticList = data;
    }
    final spotsDrivers =
        _generateSpots(data, 'day', 'dailyTotalDrivers', start, end);
    final spotsNotes =
        _generateSpots(data, 'day', 'dailyMatchingNotes', start, end);
    if (isCompare) {
      chartDataDriversCompare = spotsDrivers;
      chartDataDriversMatchingNotesCompare = spotsNotes;
    } else {
      chartDataDrivers = spotsDrivers;
      chartDataDriversMatchingNotes = spotsNotes;
    }
  }

  // ─── 🔥 DYNAMIC: Employee Notes ───────────────────────────
  Future<void> fetchEmployeeDynamic({bool isCompare = false}) async {
    try {
      final start = isCompare ? compareStartDate! : startDate!;
      final end = isCompare ? compareEndDate! : endDate!;
      final res = await CRUD().get(
          link: AppLink.getEmployeeStatic, payload: _getPayload(start, end));
      if (res == 'failure') return;
      final json = jsonDecode(res) as Map<String, dynamic>;
      if (json['status'] == 'failure') return;
      final List<dynamic> data = json['message'];
      if (data.isEmpty) return;

      final dateNameMap = _parseDateNameMap(data);
      final spotsMap = _generateEmployeeSpots(dateNameMap, start, end);

      // Merge into employee data map
      final target = isCompare ? employeeDataCompare : employeeData;

      spotsMap.forEach((name, spots) {
        final color = _getOrAssignColor(name);
        if (target.containsKey(name)) {
          target[name] = target[name]!.copyWith(notesSpots: spots);
        } else {
          target[name] = EmployeeChartData(
            name: name,
            color: color,
            notesSpots: spots,
            callsSpots: [],
          );
        }
      });
    } catch (e) {
      Log.print('Error in fetchEmployeeDynamic: $e');
    }
  }

  // ─── 🔥 DYNAMIC: Employee Calls ───────────────────────────
  Future<void> fetchEditorCallsDynamic({bool isCompare = false}) async {
    try {
      final start = isCompare ? compareStartDate! : startDate!;
      final end = isCompare ? compareEndDate! : endDate!;
      final res = await CRUD().get(
          link: AppLink.getEditorStatsCalls, payload: _getPayload(start, end));
      if (res == 'failure') return;
      final json = jsonDecode(res) as Map<String, dynamic>;
      if (json['status'] == 'failure') return;
      final List<dynamic> data = json['message'];
      if (data.isEmpty) return;

      final dateNameMap = _parseDateNameMap(data);
      final spotsMap = _generateEmployeeSpots(dateNameMap, start, end);

      final target = isCompare ? employeeDataCompare : employeeData;

      spotsMap.forEach((name, spots) {
        final color = _getOrAssignColor(name);
        if (target.containsKey(name)) {
          target[name] = target[name]!.copyWith(callsSpots: spots);
        } else {
          target[name] = EmployeeChartData(
            name: name,
            color: color,
            notesSpots: [],
            callsSpots: spots,
          );
        }
      });
    } catch (e) {
      Log.print('Error in fetchEditorCallsDynamic: $e');
    }
  }

  // ─── Employment Stats ─────────────────────────────────────
  Future<void> fetchEmploymentStats() async {
    try {
      final res = await CRUD().get(
          link: AppLink.getEmployeeDriverAfterCallingRegister,
          payload: _getPayload(startDate!, endDate!));
      if (res == 'failure') return;
      final json = jsonDecode(res);
      if (json['status'] != 'success') return;
      final List<dynamic> data = json['message']?['data'] ?? [];

      // Aggregate by name (dynamic — no hardcoded allowed list)
      final Map<String, int> aggregated = {};
      for (var item in data) {
        final name = item['employmentType'].toString().toLowerCase().trim();
        final count = int.tryParse(item['count'].toString()) ?? 0;
        aggregated[name] = (aggregated[name] ?? 0) + count;
      }

      employmentStatsList = aggregated.entries.map((e) {
        return EmploymentStat(
          name: e.key,
          count: e.value,
          color: _getOrAssignColor(e.key),
        );
      }).toList()
        ..sort((a, b) => b.count.compareTo(a.count)); // sort descending
    } catch (e) {
      Log.print("Error fetchEmploymentStats: $e");
    }
  }

  // ─── Daily Notes ──────────────────────────────────────────
  Future<void> fetchDailyNotes(DateTime date) async {
    try {
      isLoadingNotes = true;
      dailyNotesList.clear();
      update();

      final res = await CRUD().post(
          link: AppLink.getNotesForEmployee,
          payload: {"date": DateFormat('yyyy-MM-dd').format(date)});

      if (res != 'failure') {
        final json = res;
        if (json['status'] == 'success') {
          dailyNotesList = json['message'];
        }
      }
    } catch (e) {
      Log.print("Error fetchDailyNotes: $e");
    } finally {
      isLoadingNotes = false;
      update();
    }
  }

  // ─── Computed Summaries for UI ────────────────────────────

  /// Returns sorted list of employees by total notes descending
  List<EmployeeChartData> get employeesSortedByNotes {
    final list = employeeData.values.toList();
    list.sort((a, b) => b.totalNotes.compareTo(a.totalNotes));
    return list;
  }

  /// Returns sorted list of employees by total calls descending
  List<EmployeeChartData> get employeesSortedByCalls {
    final list = employeeData.values.toList();
    list.sort((a, b) => b.totalCalls.compareTo(a.totalCalls));
    return list;
  }

  /// Grand total notes across all employees
  int get grandTotalNotes =>
      employeeData.values.fold(0, (s, e) => s + e.totalNotes);

  /// Grand total calls across all employees
  int get grandTotalCalls =>
      employeeData.values.fold(0, (s, e) => s + e.totalCalls);
}
