import 'dart:developer';

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:jiffy/jiffy.dart';
import 'package:rrule_generator/rrule_generator.dart';
import 'package:time_range_picker/time_range_picker.dart';

import '../../../../api/mhsl/custom_timetable_event/custom_timetable_event.dart';
import '../../../../extensions/date_time.dart';
import '../../../../state/app/modules/timetable/bloc/timetable_bloc.dart';
import '../../../../widget/async_action_button.dart';
import '../../../../widget/demo_restricted.dart';
import '../../../../widget/focus_behaviour.dart';
import '../data/subject_color_palette.dart';
import '../subject_colors/subject_color_picker.dart';

class CustomEventEditDialog extends StatefulWidget {
  final CustomTimetableEvent? existingEvent;
  final DateTime? initialStart;
  final DateTime? initialEnd;
  final String? initialTitle;
  final String? initialDescription;
  final bool? initialAllDay;

  const CustomEventEditDialog({
    this.existingEvent,
    this.initialStart,
    this.initialEnd,
    this.initialTitle,
    this.initialDescription,
    this.initialAllDay,
    super.key,
  });

  @override
  State<CustomEventEditDialog> createState() => _CustomEventEditDialogState();
}

class _CustomEventEditDialogState extends State<CustomEventEditDialog> {
  // Selectable window for non-all-day events. Times outside this range are
  // clamped in. For events outside school hours, use the all-day toggle.
  static const TimeOfDay _windowStart = TimeOfDay(hour: 8, minute: 0);
  static const TimeOfDay _windowEnd = TimeOfDay(hour: 16, minute: 30);
  static const TimeOfDay _defaultStart = _windowStart;
  static const TimeOfDay _defaultEnd = TimeOfDay(hour: 9, minute: 30);
  static const int _minDurationMinutes = 15;

  late DateTime _date =
      widget.existingEvent?.startDate ?? widget.initialStart ?? DateTime.now();
  late TimeOfDay _startTime;
  late TimeOfDay _endTime;
  late bool _isAllDay;
  late final TextEditingController _name = TextEditingController(
    text: widget.existingEvent?.title ?? widget.initialTitle,
  );
  late final TextEditingController _description = TextEditingController(
    text: widget.existingEvent?.description ?? widget.initialDescription,
  );
  late String _rrule = widget.existingEvent?.rrule ?? '';
  // Für neue Termine standardmäßig an, bestehende behalten ihren Wert. Migrierte
  // mhsl-Termine tragen ausgeschaltet (Modell-Default), da sie nie ein Serien-
  // Ferien-Flag hatten.
  late bool _skipHolidays = widget.existingEvent?.skipHolidays ?? true;
  late SubjectColor _color =
      SubjectColorPalette.fromName(widget.existingEvent?.color) ??
      SubjectColorPalette.defaultColor;

  bool get _isEditing => widget.existingEvent != null;

  @override
  void initState() {
    super.initState();
    if (_isEditing) {
      final s = widget.existingEvent!.startDate;
      final e = widget.existingEvent!.endDate;
      _isAllDay = isAllDayConvention(s, e);
      if (_isAllDay) {
        _startTime = _defaultStart;
        _endTime = _defaultEnd;
      } else {
        final clamped = _clampToVisibleWindow(s.toTimeOfDay(), e.toTimeOfDay());
        _startTime = clamped.$1;
        _endTime = clamped.$2;
      }
      return;
    }
    _isAllDay = widget.initialAllDay ?? false;
    if (_isAllDay) {
      _startTime = _defaultStart;
      _endTime = _defaultEnd;
    } else {
      final rawStart = widget.initialStart?.toTimeOfDay() ?? _defaultStart;
      final rawEnd = widget.initialEnd?.toTimeOfDay() ?? _defaultEnd;
      final clamped = _clampToVisibleWindow(rawStart, rawEnd);
      _startTime = clamped.$1;
      _endTime = clamped.$2;
    }
  }

  static (TimeOfDay, TimeOfDay) _clampToVisibleWindow(
    TimeOfDay rawStart,
    TimeOfDay rawEnd,
  ) {
    int toMin(TimeOfDay t) => t.hour * 60 + t.minute;
    TimeOfDay fromMin(int m) => TimeOfDay(hour: m ~/ 60, minute: m % 60);

    final windowStart = toMin(_windowStart);
    final windowEnd = toMin(_windowEnd);
    var start = toMin(
      rawStart,
    ).clamp(windowStart, windowEnd - _minDurationMinutes);
    var end = toMin(rawEnd);
    if (end < start + _minDurationMinutes) end = start + _minDurationMinutes;
    if (end > windowEnd) {
      end = windowEnd;
      start = end - _minDurationMinutes;
    }
    return (fromMin(start), fromMin(end));
  }

  /// All-day convention shared with [TimetableAppointmentFactory]: a custom
  /// event is treated as all-day when its start and end both land on midnight
  /// of the same day. We piggyback on this so we don't need a backend schema
  /// change.
  static bool isAllDayConvention(DateTime start, DateTime end) =>
      start.year == end.year &&
      start.month == end.month &&
      start.day == end.day &&
      start.hour == 0 &&
      start.minute == 0 &&
      start.second == 0 &&
      end.hour == 0 &&
      end.minute == 0 &&
      end.second == 0;

  Future<void> _save() async {
    if (guardDemoAction(context)) return;
    if (_name.text.trim().isEmpty) {
      throw Exception('Bitte einen Terminnamen eingeben.');
    }

    // All-day convention: store start and end as midnight of the chosen day.
    // The factory recognises this on read.
    final midnight = DateTime(_date.year, _date.month, _date.day);
    final startDate = _isAllDay ? midnight : _date.withTime(_startTime);
    final endDate = _isAllDay ? midnight : _date.withTime(_endTime);

    final edited = CustomTimetableEvent(
      id: widget.existingEvent?.id ?? '',
      title: _name.text,
      description: _description.text,
      startDate: startDate,
      endDate: endDate,
      color: _color.name,
      rrule: _rrule,
      skipHolidays: _rrule.trim().isEmpty ? false : _skipHolidays,
      createdAt: DateTime.now(),
      updatedAt: DateTime.now(),
    );

    final bloc = context.read<TimetableBloc>();
    if (_isEditing) {
      await bloc.updateCustomEvent(widget.existingEvent!.id, edited);
    } else {
      await bloc.addCustomEvent(edited);
    }
  }

  Future<void> _pickDate() async {
    final now = DateTime.now();
    final defaultFirst = now.subtract(const Duration(days: 30));
    final defaultLast = now.add(const Duration(days: 365));
    final picked = await showDatePicker(
      context: context,
      initialDate: _date,
      firstDate: _date.isBefore(defaultFirst) ? _date : defaultFirst,
      lastDate: _date.isAfter(defaultLast) ? _date : defaultLast,
    );
    if (picked != null && picked != _date) setState(() => _date = picked);
  }

  Future<void> _pickTimeRange() async {
    final range = await showTimeRangePicker(
      context: context,
      start: _startTime,
      end: _endTime,
      disabledTime: TimeRange(startTime: _windowEnd, endTime: _windowStart),
      disabledColor: Colors.grey,
      paintingStyle: PaintingStyle.fill,
      interval: const Duration(minutes: 5),
      fromText: 'Beginnend',
      toText: 'Endend',
      strokeColor: Theme.of(context).colorScheme.secondary,
      minDuration: Duration(minutes: _minDurationMinutes),
      selectedColor: Theme.of(context).primaryColor,
      ticks: 24,
    );
    if (range is! TimeRange) return;
    setState(() {
      _startTime = range.startTime;
      _endTime = range.endTime;
    });
  }

  @override
  Widget build(BuildContext context) => AlertDialog(
    insetPadding: const EdgeInsets.all(20),
    contentPadding: const EdgeInsets.all(10),
    title: Text(_isEditing ? 'Termin bearbeiten' : 'Termin hinzufügen'),
    content: SingleChildScrollView(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          ListTile(
            title: TextField(
              controller: _name,
              autofocus: true,
              decoration: const InputDecoration(
                labelText: 'Terminname',
                border: OutlineInputBorder(),
              ),
              onTapOutside: (_) => FocusBehaviour.textFieldTapOutside(context),
            ),
          ),
          ListTile(
            title: TextField(
              controller: _description,
              maxLines: 2,
              minLines: 2,
              decoration: const InputDecoration(
                labelText: 'Beschreibung',
                border: OutlineInputBorder(),
              ),
              onTapOutside: (_) => FocusBehaviour.textFieldTapOutside(context),
            ),
          ),
          const Divider(),
          ListTile(
            leading: const Icon(Icons.date_range_outlined),
            title: Text(Jiffy.parseFromDateTime(_date).yMMMd),
            subtitle: const Text('Datum'),
            onTap: _pickDate,
          ),
          SwitchListTile(
            secondary: const Icon(Icons.today_outlined),
            title: const Text('Ganztägig'),
            value: _isAllDay,
            onChanged: (v) => setState(() => _isAllDay = v),
          ),
          if (!_isAllDay)
            ListTile(
              leading: const Icon(Icons.access_time_outlined),
              title: Text(
                '${_startTime.format(context)} - ${_endTime.format(context)}',
              ),
              subtitle: const Text('Zeitraum'),
              onTap: _pickTimeRange,
            ),
          const Divider(),
          ListTile(
            leading: const Icon(Icons.color_lens_outlined),
            title: const Text('Farbgebung'),
            subtitle: Text(SubjectColorPalette.displayOf(_color).displayName),
            trailing: Icon(
              Icons.circle,
              color: SubjectColorPalette.displayOf(_color).color,
            ),
            onTap: () => showTimetableColorPicker(
              context,
              title: 'Farbe wählen',
              current: _color,
              // Rein lokal (kein Netzwerk) — Sheet schließt sofort, gespeichert
              // wird erst beim „Speichern" des Termins.
              onSelected: (option) async => setState(() => _color = option),
            ),
          ),
          const Divider(),
          // RRuleGenerator has zero outer padding; wrap it in the ListTile
          // default 16px indent so it aligns with the rows above.
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
            child: RRuleGenerator(
              config: _rruleConfig(context),
              initialRRule: _rrule,
              locale: RRuleLocale.de_DE,
              onChange: (newValue) {
                log('Rule: $newValue');
                setState(() => _rrule = newValue);
              },
            ),
          ),
          if (_rrule.trim().isNotEmpty) ...[
            const Divider(),
            SwitchListTile(
              secondary: const Icon(Icons.beach_access_outlined),
              title: const Text('In den Ferien ausblenden'),
              subtitle: const Text(
                'An Ferientagen wird der Termin nicht angezeigt',
              ),
              value: _skipHolidays,
              onChanged: (v) => setState(() => _skipHolidays = v),
            ),
          ],
        ],
      ),
    ),
    actions: [
      AsyncDialogAction(
        confirmLabel: _isEditing ? 'Speichern' : 'Erstellen',
        onConfirm: _save,
      ),
    ],
  );

  /// Maps the rrule_generator widget onto the app's Material theme so its
  /// pickers blend in. Styling is limited to what `RRuleGeneratorConfig`
  /// exposes — fully custom styling isn't possible without forking it.
  RRuleGeneratorConfig _rruleConfig(BuildContext context) {
    final theme = Theme.of(context);
    final cs = theme.colorScheme;
    return RRuleGeneratorConfig(
      headerStyle: RRuleHeaderStyle(
        textStyle:
            theme.textTheme.titleSmall?.copyWith(
              fontWeight: FontWeight.w600,
              color: cs.onSurface,
            ) ??
            const TextStyle(),
      ),
      labelStyle:
          theme.textTheme.bodyMedium?.copyWith(color: cs.onSurfaceVariant) ??
          const TextStyle(),
      inputTextStyle: RRuleInputTextStyle(
        inputTextDecoration: const InputDecoration(
          isDense: true,
          border: OutlineInputBorder(),
        ),
        inputTextStyle:
            theme.textTheme.bodyMedium ?? const TextStyle(),
      ),
      dropdownStyle: RRuleDropdownStyle(
        dropdownMenuTextStyle: theme.textTheme.bodyMedium ?? const TextStyle(),
        dropdownMenuItemTextStyle:
            theme.textTheme.bodyMedium ?? const TextStyle(),
      ),
      switchStyle: RRuleSwitchStyle(
        thumbColor: cs.onPrimary,
        activeTrackColor: cs.primary,
        inactiveTrackColor: cs.surfaceContainerHighest,
        trackOutlineWidth: 0,
        trackOutlineColor: Colors.transparent,
        switchTextStyle:
            theme.textTheme.bodyMedium?.copyWith(
              color: cs.onSurface,
            ) ??
            const TextStyle(),
      ),
      selectDayStyle: RRuleSelectDayStyle(
        dayStyle: BoxDecoration(
          shape: BoxShape.circle,
          color: cs.surfaceContainerHighest,
        ),
        dayTextStyle: TextStyle(color: cs.onSurface),
        selectedDayStyle: BoxDecoration(
          shape: BoxShape.circle,
          color: cs.primary,
        ),
        selectedDayTextStyle: TextStyle(color: cs.onPrimary),
      ),
      datePickerStyle: RRuleDatePickerStyle(
        datePickerTextStyle: theme.textTheme.bodyMedium ?? const TextStyle(),
      ),
      divider: const Divider(height: 1),
    );
  }
}
