import 'package:flutter_test/flutter_test.dart';
import 'package:jiffy/jiffy.dart';
import 'package:marianum_mobile/extensions/date_time.dart';

void main() {
  setUpAll(() async {
    // Jiffy needs locale data once before any formatting calls.
    await Jiffy.setLocale('de');
  });

  group('IsSameDay', () {
    test('isSameDay matches by year/month/day, ignoring time', () {
      final a = DateTime(2026, 5, 8, 9, 30);
      final b = DateTime(2026, 5, 8, 22, 0);
      expect(a.isSameDay(b), isTrue);
    });

    test('isSameDay differs across midnight', () {
      final a = DateTime(2026, 5, 8, 23, 59);
      final b = DateTime(2026, 5, 9, 0, 0);
      expect(a.isSameDay(b), isFalse);
    });

    test('isSameOrAfter is inclusive', () {
      final a = DateTime(2026, 5, 8, 12);
      final b = DateTime(2026, 5, 8, 12);
      expect(a.isSameOrAfter(b), isTrue);
      expect(a.add(const Duration(seconds: 1)).isSameOrAfter(b), isTrue);
      expect(a.subtract(const Duration(seconds: 1)).isSameOrAfter(b), isFalse);
    });
  });

  group('CalendarDayArithmetic', () {
    test('addDays preserves wall-clock time on a normal day', () {
      final d = DateTime(2026, 5, 18, 8, 30);
      final next = d.addDays(1);
      expect(next.year, 2026);
      expect(next.month, 5);
      expect(next.day, 19);
      expect(next.hour, 8);
      expect(next.minute, 30);
    });

    test('addDays normalises across month boundaries', () {
      final d = DateTime(2026, 1, 30);
      expect(d.addDays(3), DateTime(2026, 2, 2));
    });

    test(
      'addDays(7 * N) keeps weekday across the October DST fall-back, '
      'unlike Duration(days: 7 * N)',
      () {
        // Sep 1, 2025 is a Monday (CEST). 16 weeks later is Dec 22, 2025,
        // also a Monday (CET) — but `add(Duration(days: 112))` lands at
        // Sunday Dec 21 23:00 because DST drains an hour in October.
        final mondayBeforeDst = DateTime(2025, 9, 1);
        final mondayAfterDst = mondayBeforeDst.addDays(7 * 16);
        expect(mondayAfterDst.weekday, DateTime.monday);
        expect(mondayAfterDst.hour, 0);
        expect(mondayAfterDst.day, 22);
        expect(mondayAfterDst.month, 12);
      },
      // Test only meaningful when running in a DST-aware timezone (e.g.
      // Europe/Berlin). In UTC `Duration` arithmetic happens to be safe
      // too, so the assertion still holds; we keep it unconditional.
    );

    test(
      'subtractDays(7) crossing March DST spring-forward stays on Monday',
      () {
        // Mon Apr 6 2026 (CEST, UTC+2) - 1 week should still be Mon Mar 30
        // 2026 (CEST). With Duration(days:7) you'd get Mar 29 23:00 (CEST)
        // - actually CET=>CEST: the transition is at 02:00 → 03:00 on
        // Mar 29. Either way `addDays`/`subtractDays` must hit midnight.
        final monApr6 = DateTime(2026, 4, 6);
        final prev = monApr6.subtractDays(7);
        expect(prev.weekday, DateTime.monday);
        expect(prev.day, 30);
        expect(prev.month, 3);
        expect(prev.hour, 0);
        expect(prev.minute, 0);
      },
    );

    test('subtractDays(n) is equivalent to addDays(-n)', () {
      final d = DateTime(2026, 5, 18, 13, 45);
      expect(d.subtractDays(5), d.addDays(-5));
    });
  });

  group('DateTimeFormatting', () {
    final dt = DateTime(2026, 5, 8, 9, 7);

    test('formatHm pads hours and minutes to two digits', () {
      expect(dt.formatHm(), '09:07');
    });

    test('formatDate uses dd.MM.yyyy', () {
      expect(dt.formatDate(), '08.05.2026');
    });

    test('formatDateTime combines date and time', () {
      expect(dt.formatDateTime(), '08.05.2026 09:07');
    });

    test('formatDateShort drops the year', () {
      expect(dt.formatDateShort(), '08.05.');
    });

    test('formatDateShortHm combines short date and time', () {
      expect(dt.formatDateShortHm(), '08.05. 09:07');
    });

    test('timeRangeTo joins start and end with a hyphen', () {
      final end = dt.add(const Duration(minutes: 45));
      expect(dt.timeRangeTo(end), '09:07 - 09:52');
    });

    test('weekKey renders a zero-padded yyyyMMdd key', () {
      expect(dt.weekKey(), '20260508');
      expect(DateTime(2026, 12, 31).weekKey(), '20261231');
    });
  });

  group('formatDateRelativeShort', () {
    final now = DateTime(2026, 5, 9, 14, 0); // Saturday

    test('today returns "Heute"', () {
      expect(
        DateTime(2026, 5, 9, 8, 0).formatDateRelativeShort(now: now),
        'Heute',
      );
    });

    test('yesterday returns "Gestern"', () {
      expect(
        DateTime(2026, 5, 8, 23, 30).formatDateRelativeShort(now: now),
        'Gestern',
      );
    });

    test('2 to 6 days ago returns the German weekday name', () {
      // 2026-05-07 is a Thursday
      expect(
        DateTime(2026, 5, 7).formatDateRelativeShort(now: now),
        'Donnerstag',
      );
      // 2026-05-03 is a Sunday (6 days before Saturday 9th)
      expect(
        DateTime(2026, 5, 3).formatDateRelativeShort(now: now),
        'Sonntag',
      );
    });

    test('7 days or more ago falls back to dd.MM.yyyy', () {
      expect(
        DateTime(2026, 5, 2).formatDateRelativeShort(now: now),
        '02.05.2026',
      );
      expect(
        DateTime(2026, 1, 1).formatDateRelativeShort(now: now),
        '01.01.2026',
      );
    });

    test('future dates fall back to dd.MM.yyyy', () {
      expect(
        DateTime(2026, 5, 10).formatDateRelativeShort(now: now),
        '10.05.2026',
      );
    });
  });
}
