import 'dart:async';
import 'dart:collection';
import 'package:flutter/material.dart';
import 'package:habo/constants.dart';
import 'package:habo/habits/habit.dart';
import 'package:habo/model/habit_data.dart';
import 'package:habo/model/category.dart';
import 'package:habo/repositories/habit_repository.dart';
import 'package:habo/repositories/event_repository.dart';
import 'package:habo/repositories/category_repository.dart';
import 'package:habo/services/backup_service.dart';
import 'package:habo/services/notification_service.dart';
import 'package:habo/services/ui_feedback_service.dart';

class HabitsManager extends ChangeNotifier {
  final HabitRepository _habitRepository;
  final EventRepository _eventRepository;
  final CategoryRepository _categoryRepository;
  final BackupService? _backupService;
  final NotificationService? _notificationService;
  final UIFeedbackService? _uiFeedbackService;

  final List<Habit> _allHabits = [];
  final List<Habit> _toDelete = [];
  final List<Category> _categories = [];

  List<Habit> get allHabits => _allHabits;
  List<Habit> get toDelete => _toDelete;
  List<Category> get categories => _categories;

  List<Habit> get activeHabits =>
      _allHabits.where((h) => !h.habitData.archived).toList();

  List<Habit> get archivedHabits =>
      _allHabits.where((h) => h.habitData.archived).toList();

  HabitsManager({
    required HabitRepository habitRepository,
    required EventRepository eventRepository,
    required CategoryRepository categoryRepository,
    BackupService? backupService,
    NotificationService? notificationService,
    UIFeedbackService? uiFeedbackService,
  })  : _habitRepository = habitRepository,
        _eventRepository = eventRepository,
        _categoryRepository = categoryRepository,
        _backupService = backupService,
        _notificationService = notificationService,
        _uiFeedbackService = uiFeedbackService;

  // ─── Initialization ────────────────────────────────────────────────────────

  Future<void> initModel() async {
    await loadHabits();
    await loadCategories();
  }

  Future<void> loadHabits() async {
    _allHabits.clear();
    final habits = await _habitRepository.getAllHabits();
    for (final habit in habits) {
      try {
        final events = await _eventRepository.getEventsMapForHabit(
          habit.habitData.id ?? 0,
        );
        habit.habitData.events = events;
      } catch (_) {
        // If event loading fails, keep empty events
      }
      _allHabits.add(habit);
    }
    _allHabits.sort((a, b) => a.habitData.position.compareTo(b.habitData.position));
    notifyListeners();
  }

  Future<void> loadCategories() async {
    _categories.clear();
    try {
      final cats = await _categoryRepository.getAllCategories();
      _categories.addAll(cats);
    } catch (_) {
      // If category loading fails, keep empty
    }
    notifyListeners();
  }

  // ─── CRUD Operations ───────────────────────────────────────────────────────

  void addHabit(
    String title,
    bool twoDayRule,
    String cue,
    String routine,
    String reward,
    bool showReward,
    bool advanced,
    bool notification,
    TimeOfDay notTime,
    String sanction,
    bool showSanction,
    String accountant, {
    HabitType habitType = HabitType.boolean,
    double targetValue = 100.0,
    double partialValue = 10.0,
    String unit = '',
  }) {
    final habitData = HabitData(
      position: _allHabits.length,
      title: title,
      twoDayRule: twoDayRule,
      cue: cue,
      routine: routine,
      reward: reward,
      showReward: showReward,
      advanced: advanced,
      notification: notification,
      notTime: notTime,
      sanction: sanction,
      showSanction: showSanction,
      accountant: accountant,
      habitType: habitType,
      targetValue: targetValue,
      partialValue: partialValue,
      unit: unit,
      events: SplayTreeMap<DateTime, List>(),
    );

    final habit = Habit(habitData: habitData);
    _habitRepository.createHabit(habit);
    _allHabits.add(habit);
    _notificationService?.resetNotifications(activeHabits);
    notifyListeners();
  }

  void editHabit(HabitData data) {
    final index = _allHabits.indexWhere((h) => h.habitData.id == data.id);
    if (index != -1) {
      _allHabits[index].habitData
        ..title = data.title
        ..twoDayRule = data.twoDayRule
        ..cue = data.cue
        ..routine = data.routine
        ..reward = data.reward
        ..showReward = data.showReward
        ..advanced = data.advanced
        ..notification = data.notification
        ..notTime = data.notTime
        ..sanction = data.sanction
        ..showSanction = data.showSanction
        ..accountant = data.accountant
        ..habitType = data.habitType
        ..targetValue = data.targetValue
        ..partialValue = data.partialValue
        ..unit = data.unit
        ..archived = data.archived;

      _habitRepository.updateHabit(_allHabits[index]);
      _notificationService?.resetNotifications(activeHabits);
      notifyListeners();
    }
  }

  void deleteHabit(int id) {
    final habit = findHabitById(id);
    if (habit != null) {
      _toDelete.add(habit);
      _allHabits.remove(habit);
      _habitRepository.deleteHabit(id);
      _notificationService?.removeNotifications(id);
      _uiFeedbackService?.showMessageWithAction(
        message: 'Habit deleted.',
        actionLabel: 'Undo',
        onActionPressed: () => undoDelete(),
        backgroundColor: Colors.red,
      );
      updateOrder();
      notifyListeners();
    }
  }

  void undoDelete() {
    if (_toDelete.isNotEmpty) {
      final habit = _toDelete.removeLast();
      _allHabits.add(habit);
      updateOrder();
      notifyListeners();
    }
  }

  // ─── Archive Operations ────────────────────────────────────────────────────

  void archiveHabit(int id) {
    final habit = findHabitById(id);
    if (habit != null) {
      habit.habitData.archived = true;
      _habitRepository.updateHabit(habit);
      _notificationService?.disableHabitNotification(id);
      _uiFeedbackService?.showMessageWithAction(
        message: 'Habit archived',
        actionLabel: 'Undo',
        onActionPressed: () => unarchiveHabit(id),
        backgroundColor: Colors.orange,
      );
      notifyListeners();
    }
  }

  void unarchiveHabit(int id) {
    final habit = findHabitById(id);
    if (habit != null) {
      habit.habitData.archived = false;
      _habitRepository.updateHabit(habit);
      if (habit.habitData.notification) {
        _notificationService?.setHabitNotification(
          id,
          habit.habitData.notTime,
          'Habo',
          habit.habitData.title,
        );
      }
      _uiFeedbackService?.showSuccess('Habit unarchived');
      notifyListeners();
    }
  }

  // ─── Reorder ───────────────────────────────────────────────────────────────

  void reorderList(int oldIndex, int newIndex) {
    if (oldIndex < newIndex) newIndex--;
    final item = _allHabits.removeAt(oldIndex);
    _allHabits.insert(newIndex, item);
    updateOrder();
    _habitRepository.updateHabitsOrder(_allHabits);
    notifyListeners();
  }

  void updateOrder() {
    for (int i = 0; i < _allHabits.length; i++) {
      _allHabits[i].habitData.position = i;
    }
    // In-memory position update only - persistence handled separately
  }

  // ─── Event Operations ──────────────────────────────────────────────────────

  void addEvent(int id, DateTime date, List event) {
    final key = DateTime(date.year, date.month, date.day);
    
    // Update in-memory if habit exists
    final habit = findHabitById(id);
    if (habit != null) {
      habit.habitData.events[key] = event;
      _updateLastStreak(habit.habitData);
    }
    
    // Always persist to repository
    try {
      _eventRepository.insertEvent(id, key, event);
    } catch (_) {}

    notifyListeners();
  }

  void deleteEvent(int id, DateTime date) {
    final key = DateTime(date.year, date.month, date.day);
    
    // Update in-memory if habit exists
    final habit = findHabitById(id);
    if (habit != null) {
      habit.habitData.events.remove(key);
      _updateLastStreak(habit.habitData);
    }
    
    // Always persist to repository
    try {
      _eventRepository.deleteEvent(id, key);
    } catch (_) {}

    notifyListeners();
  }

  void _updateLastStreak(HabitData data) {
    final events = data.events;
    if (events.isEmpty) {
      data.streak = 0;
      data.streakVisible = false;
      data.orangeStreak = false;
      return;
    }

    if (data.twoDayRule) {
      _updateLastStreakTwoDay(data);
    } else {
      _updateLastStreakNormal(data);
    }
  }

  void _updateLastStreakNormal(HabitData data) {
    final events = data.events;
    if (events.isEmpty) {
      data.streak = 0;
      data.streakVisible = false;
      data.orangeStreak = false;
      return;
    }

    int inStreak = 0;
    final dates = events.keys.toList().reversed.toList();

    for (int i = 0; i < dates.length; i++) {
      final date = dates[i];
      final event = events[date]!;
      final dayType = event[0] is DayType ? event[0] as DayType : DayType.values[event[0] as int];

      if (dayType == DayType.clear) continue;

      if (i > 0) {
        final prevDate = dates[i - 1];
        final diff = prevDate.difference(date).inDays;
        if (diff > 1) break;
      }

      if (dayType == DayType.check) {
        inStreak++;
      } else if (dayType == DayType.progress && event.length >= 4) {
        final progressValue = event[2] as double? ?? 0.0;
        final target = event[3] as double? ?? data.targetValue;
        if (progressValue >= target) inStreak++;
      } else if (dayType == DayType.fail || dayType == DayType.skip) {
        break;
      }
    }

    data.streak = inStreak;
    data.streakVisible = inStreak >= 2;
    data.orangeStreak = false;
  }

  void _updateLastStreakTwoDay(HabitData data) {
    final events = data.events;
    if (events.isEmpty) {
      data.streak = 0;
      data.streakVisible = false;
      data.orangeStreak = false;
      return;
    }

    int inStreak = 0;
    bool usingTwoDayRule = false;
    final dates = events.keys.toList().reversed.toList();

    for (int i = 0; i < dates.length; i++) {
      final date = dates[i];
      final event = events[date]!;
      final dayType = event[0] is DayType ? event[0] as DayType : DayType.values[event[0] as int];

      if (dayType == DayType.clear) continue;

      if (i > 0) {
        final prevDate = dates[i - 1];
        final diff = prevDate.difference(date).inDays;
        if (diff > 1) break;
      }

      if (dayType == DayType.check) {
        inStreak++;
        usingTwoDayRule = false;
      } else if (dayType == DayType.progress && event.length >= 4) {
        final progressValue = event[2] as double? ?? 0.0;
        final target = event[3] as double? ?? data.targetValue;
        if (progressValue >= target) {
          inStreak++;
          usingTwoDayRule = false;
        }
      } else if (dayType == DayType.fail) {
        if (usingTwoDayRule) {
          break;
        }
        usingTwoDayRule = true;
      } else if (dayType == DayType.skip) {
        if (usingTwoDayRule) break;
        // Skip doesn't affect streak
      }
    }

    data.streak = inStreak;
    data.streakVisible = inStreak >= 2;
    data.orangeStreak = usingTwoDayRule;
  }

  // ─── Utility ───────────────────────────────────────────────────────────────

  Habit? findHabitById(int id) {
    try {
      return _allHabits.firstWhere((h) => h.habitData.id == id);
    } catch (_) {
      return null;
    }
  }

  String getNameOfHabit(int id) {
    final habit = findHabitById(id);
    return habit?.habitData.title ?? '';
  }

  // ─── Backup & Widget ───────────────────────────────────────────────────────

  Future<void> createBackup() async {
    await _backupService?.createDatabaseBackup();
  }

  Future<void> loadBackup(String path) async {
    await _backupService?.loadBackup(path);
    await loadHabits();
  }

  void resetNotifications([List<dynamic>? habits]) {
    _notificationService?.resetNotifications(habits ?? activeHabits);
  }

  void updateHomeWidget() {
    // Stub
  }

  // ─── Category ──────────────────────────────────────────────────────────────

  Future<void> addCategory(Category category) async {
    await _categoryRepository.createCategory(category);
    await loadCategories();
  }

  Future<void> updateCategory(Category category) async {
    await _categoryRepository.updateCategory(category);
    await loadCategories();
  }

  Future<void> deleteCategory(int id) async {
    await _categoryRepository.deleteCategory(id);
    await loadCategories();
  }
}
