import 'package:flutter_test/flutter_test.dart';
import 'package:sharedinbox/core/sieve/sieve_actions.dart';
import 'package:sharedinbox/core/sieve/sieve_conditions.dart';
import 'package:sharedinbox/core/sieve/sieve_interpreter.dart';
import 'package:sharedinbox/core/sieve/sieve_rule.dart';

SieveEmailContext _email({
  String subject = '',
  String from = '',
  String to = '',
  int size = 0,
  Map<String, List<String>> extra = const {},
}) {
  return SieveEmailContext(
    headers: {
      if (subject.isNotEmpty) 'subject': [subject],
      if (from.isNotEmpty) 'from': [from],
      if (to.isNotEmpty) 'to': [to],
      ...extra,
    },
    sizeBytes: size,
  );
}

void main() {
  final interp = SieveInterpreter();

  group('SieveInterpreter — no rules', () {
    test('empty rule list keeps inbox', () {
      final ctx = interp.execute([], _email());
      expect(ctx.keepInInbox, isTrue);
      expect(ctx.isCancelled, isFalse);
    });
  });

  group('HeaderCondition :contains', () {
    test('matches subject substring (case-insensitive)', () {
      final rules = [
        SieveRule(
          joinType: 'single',
          conditions: [
            HeaderCondition(['subject'], ':contains', ['spam']),
          ],
          actions: [DiscardAction()],
        ),
      ];

      final ctx = interp.execute(rules, _email(subject: 'This is SPAM!'));
      expect(ctx.isCancelled, isTrue);
      expect(ctx.keepInInbox, isFalse);
    });

    test('does not match unrelated subject', () {
      final rules = [
        SieveRule(
          joinType: 'single',
          conditions: [
            HeaderCondition(['subject'], ':contains', ['spam']),
          ],
          actions: [DiscardAction()],
        ),
      ];

      final ctx = interp.execute(rules, _email(subject: 'Meeting tomorrow'));
      expect(ctx.isCancelled, isFalse);
      expect(ctx.keepInInbox, isTrue);
    });
  });

  group('HeaderCondition :is', () {
    test('exact match on from header', () {
      final rules = [
        SieveRule(
          joinType: 'single',
          conditions: [
            HeaderCondition(['from', 'reply-to'], ':is', ['boss@work.com']),
          ],
          actions: [
            FlagAction([r'\Important']),
            FileIntoAction('Work'),
          ],
        ),
      ];

      final ctx = interp.execute(rules, _email(from: 'boss@work.com'));
      expect(ctx.flagsToAdd, contains(r'\Important'));
      expect(ctx.targetFolders, contains('Work'));
      expect(ctx.keepInInbox, isFalse);
    });

    test('does not match partial from address', () {
      final rules = [
        SieveRule(
          joinType: 'single',
          conditions: [
            HeaderCondition(['from'], ':is', ['boss@work.com']),
          ],
          actions: [FileIntoAction('Work')],
        ),
      ];

      final ctx = interp.execute(rules, _email(from: 'other-boss@work.com'));
      expect(ctx.targetFolders, isEmpty);
      expect(ctx.keepInInbox, isTrue);
    });
  });

  group('HeaderCondition :matches (glob)', () {
    test('* matches any substring', () {
      final rules = [
        SieveRule(
          joinType: 'single',
          conditions: [
            HeaderCondition(['subject'], ':matches', ['*newsletter*']),
          ],
          actions: [FileIntoAction('Bulk')],
        ),
      ];

      final ctx = interp.execute(
        rules,
        _email(subject: 'Weekly Newsletter Issue'),
      );
      expect(ctx.targetFolders, contains('Bulk'));
    });
  });

  group('SizeCondition', () {
    test(':over threshold fires', () {
      final rules = [
        SieveRule(
          joinType: 'single',
          conditions: [SizeCondition(':over', 1024)],
          actions: [FileIntoAction('Large')],
        ),
      ];
      final ctx = interp.execute(rules, _email(size: 2048));
      expect(ctx.targetFolders, contains('Large'));
    });

    test(':under threshold fires', () {
      final rules = [
        SieveRule(
          joinType: 'single',
          conditions: [SizeCondition(':under', 500)],
          actions: [FileIntoAction('Small')],
        ),
      ];
      final ctx = interp.execute(rules, _email(size: 100));
      expect(ctx.targetFolders, contains('Small'));
    });

    test(':over threshold does not fire when size equals threshold', () {
      final rules = [
        SieveRule(
          joinType: 'single',
          conditions: [SizeCondition(':over', 1024)],
          actions: [DiscardAction()],
        ),
      ];
      final ctx = interp.execute(rules, _email(size: 1024));
      expect(ctx.isCancelled, isFalse);
    });
  });

  group('allof / anyof join types', () {
    test('allof fires only when all conditions match', () {
      final rules = [
        SieveRule(
          joinType: 'allof',
          conditions: [
            HeaderCondition(['subject'], ':contains', ['deal']),
            HeaderCondition(['from'], ':contains', ['shop.com']),
          ],
          actions: [FileIntoAction('Deals')],
        ),
      ];

      // Both match.
      var ctx = interp.execute(
        rules,
        _email(subject: 'Big deal today', from: 'offers@shop.com'),
      );
      expect(ctx.targetFolders, contains('Deals'));

      // Only subject matches.
      ctx = interp.execute(
        rules,
        _email(subject: 'Big deal today', from: 'friend@example.com'),
      );
      expect(ctx.targetFolders, isEmpty);
    });

    test('anyof fires when any condition matches', () {
      final rules = [
        SieveRule(
          joinType: 'anyof',
          conditions: [
            HeaderCondition(['subject'], ':contains', ['spam']),
            HeaderCondition(['subject'], ':contains', ['advertisement']),
          ],
          actions: [DiscardAction()],
        ),
      ];

      final ctx = interp.execute(rules, _email(subject: 'Huge advertisement!'));
      expect(ctx.isCancelled, isTrue);
    });
  });

  group('discard stops processing', () {
    test('rules after discard are skipped', () {
      final rules = [
        SieveRule(
          joinType: 'single',
          conditions: [
            HeaderCondition(['subject'], ':contains', ['spam']),
          ],
          actions: [DiscardAction()],
        ),
        SieveRule(
          joinType: 'single',
          conditions: const [],
          actions: [FileIntoAction('Inbox')],
        ),
      ];

      final ctx = interp.execute(rules, _email(subject: 'Spam'));
      expect(ctx.isCancelled, isTrue);
      expect(ctx.targetFolders, isEmpty);
    });
  });

  group('MarkAsSeenAction', () {
    test('adds \\Seen flag', () {
      final rules = [
        SieveRule(
          joinType: 'single',
          conditions: const [],
          actions: [MarkAsSeenAction()],
        ),
      ];
      final ctx = interp.execute(rules, _email());
      expect(ctx.flagsToAdd, contains(r'\Seen'));
    });
  });

  group('if/elsif/else branch groups', () {
    final rules = [
      SieveRule(
        joinType: 'single',
        conditions: [
          HeaderCondition(['subject'], ':contains', ['spam']),
        ],
        actions: [DiscardAction()],
        branchGroupId: 1,
      ),
      SieveRule(
        joinType: 'single',
        conditions: [
          HeaderCondition(['subject'], ':contains', ['work']),
        ],
        actions: [FileIntoAction('Work')],
        branchGroupId: 1,
      ),
      SieveRule(
        joinType: 'single',
        conditions: const [],
        actions: [KeepAction()],
        branchGroupId: 1,
        isElseBranch: true,
      ),
    ];

    test('first branch fires, subsequent branches are skipped', () {
      final ctx = interp.execute(rules, _email(subject: 'spam message'));
      expect(ctx.isCancelled, isTrue);
      expect(ctx.targetFolders, isEmpty);
    });

    test('second branch fires when first does not match', () {
      final ctx = interp.execute(rules, _email(subject: 'Work update'));
      expect(ctx.isCancelled, isFalse);
      expect(ctx.targetFolders, contains('Work'));
    });

    test('else branch fires when no branch matched', () {
      final ctx = interp.execute(rules, _email(subject: 'Hello'));
      expect(ctx.isCancelled, isFalse);
      expect(ctx.targetFolders, isEmpty);
      expect(ctx.keepInInbox, isTrue);
    });
  });

  group('multiple independent rules', () {
    test('both rules fire when conditions match', () {
      final rules = [
        SieveRule(
          joinType: 'single',
          conditions: [
            HeaderCondition(['subject'], ':contains', ['invoice']),
          ],
          actions: [FileIntoAction('Finance')],
        ),
        SieveRule(
          joinType: 'single',
          conditions: [
            HeaderCondition(['from'], ':contains', ['boss@']),
          ],
          actions: [
            FlagAction([r'\Important']),
          ],
        ),
      ];

      final ctx = interp.execute(
        rules,
        _email(subject: 'Invoice #123', from: 'boss@corp.com'),
      );
      expect(ctx.targetFolders, contains('Finance'));
      expect(ctx.flagsToAdd, contains(r'\Important'));
    });
  });

  group('implicit keep', () {
    test('keepInInbox stays true when no action changes routing', () {
      final rules = [
        SieveRule(
          joinType: 'single',
          conditions: [
            HeaderCondition(['subject'], ':contains', ['nope']),
          ],
          actions: [DiscardAction()],
        ),
      ];
      final ctx = interp.execute(rules, _email(subject: 'Hi there'));
      expect(ctx.keepInInbox, isTrue);
      expect(ctx.isCancelled, isFalse);
    });
  });
}
