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_parser.dart';

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

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

  SieveExecutionContext run(String script, SieveEmailContext email) {
    return interp.execute(parser.parse(script), email);
  }

  group('SieveParser — require', () {
    test('require is parsed without error', () {
      expect(
        () => parser.parse('require ["fileinto", "imap4flags"];'),
        returnsNormally,
      );
    });

    test('require produces no rules', () {
      final rules = parser.parse('require ["fileinto"];');
      expect(rules, isEmpty);
    });
  });

  group('SieveParser — basic if/discard', () {
    test('discard on subject :contains', () {
      const script = '''
require ["fileinto"];
if header :contains "Subject" "Spam" {
  discard;
}
''';
      var ctx = run(script, _email(subject: 'This is Spam!'));
      expect(ctx.isCancelled, isTrue);

      ctx = run(script, _email(subject: 'Hello'));
      expect(ctx.isCancelled, isFalse);
    });
  });

  group('SieveParser — fileinto + flag', () {
    test('flag and fileinto on :is match across multiple headers', () {
      const script = '''
require ["fileinto", "imap4flags"];
if header :is ["From", "Reply-To"] "boss@work.com" {
  flag ["\\\\Important"];
  fileinto "Work";
}
''';
      final ctx = run(script, _email(from: 'boss@work.com'));
      expect(ctx.targetFolders, contains('Work'));
      expect(ctx.keepInInbox, isFalse);
    });

    test('no match means inbox is kept', () {
      const script = '''
if header :is "From" "boss@work.com" {
  fileinto "Work";
}
''';
      final ctx = run(script, _email(from: 'other@example.com'));
      expect(ctx.targetFolders, isEmpty);
      expect(ctx.keepInInbox, isTrue);
    });
  });

  group('SieveParser — anyof', () {
    test('anyof fires when any sub-condition matches', () {
      const script = '''
if anyof (
  header :contains "Subject" "Newsletter",
  header :contains "Subject" "Promotion"
) {
  fileinto "Bulk";
}
''';
      var ctx = run(script, _email(subject: 'Weekly Newsletter'));
      expect(ctx.targetFolders, contains('Bulk'));

      ctx = run(script, _email(subject: 'Big Promotion Inside'));
      expect(ctx.targetFolders, contains('Bulk'));

      ctx = run(script, _email(subject: 'Normal message'));
      expect(ctx.targetFolders, isEmpty);
    });
  });

  group('SieveParser — allof', () {
    test('allof fires only when all conditions match', () {
      const script = '''
if allof (
  header :contains "From" "shop.com",
  header :contains "Subject" "deal"
) {
  fileinto "Deals";
}
''';
      var ctx = run(
        script,
        _email(from: 'offers@shop.com', subject: 'Hot deal today'),
      );
      expect(ctx.targetFolders, contains('Deals'));

      ctx = run(
        script,
        _email(from: 'friend@example.com', subject: 'Hot deal today'),
      );
      expect(ctx.targetFolders, isEmpty);
    });
  });

  group('SieveParser — size condition', () {
    test(':over with K suffix', () {
      const script = '''
if size :over 100K {
  fileinto "Large";
}
''';
      var ctx = run(script, _email(size: 200 * 1024));
      expect(ctx.targetFolders, contains('Large'));

      ctx = run(script, _email(size: 50 * 1024));
      expect(ctx.targetFolders, isEmpty);
    });

    test(':under fires for small messages', () {
      const script = '''
if size :under 1K {
  fileinto "Tiny";
}
''';
      final ctx = run(script, _email(size: 500));
      expect(ctx.targetFolders, contains('Tiny'));
    });
  });

  group('SieveParser — if/elsif/else', () {
    const script = '''
if header :contains "Subject" "Spam" {
  discard;
} elsif header :contains "Subject" "Work" {
  fileinto "Work";
} else {
  keep;
}
''';

    test('if branch fires', () {
      final ctx = run(script, _email(subject: 'Spam message'));
      expect(ctx.isCancelled, isTrue);
    });

    test('elsif branch fires when if does not match', () {
      final ctx = run(script, _email(subject: 'Work update'));
      expect(ctx.isCancelled, isFalse);
      expect(ctx.targetFolders, contains('Work'));
    });

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

  group('SieveParser — multiple independent if blocks', () {
    test('both fire independently', () {
      const script = '''
if header :contains "Subject" "invoice" {
  fileinto "Finance";
}
if header :contains "From" "boss@" {
  flag ["\\\\Important"];
}
''';
      final ctx = run(
        script,
        _email(subject: 'Invoice #42', from: 'boss@corp.com'),
      );
      expect(ctx.targetFolders, contains('Finance'));
      expect(ctx.flagsToAdd, contains(r'\Important'));
    });
  });

  group('SieveParser — keep and stop', () {
    test('keep action keeps inbox', () {
      const script = 'keep;';
      final ctx = run(script, _email());
      expect(ctx.keepInInbox, isTrue);
      expect(ctx.isCancelled, isFalse);
    });

    test('stop acts as implicit keep', () {
      const script = 'stop;';
      final ctx = run(script, _email());
      expect(ctx.keepInInbox, isTrue);
    });
  });

  group('SieveParser — comments', () {
    test('line comments are ignored', () {
      const script = '''
# This is a comment
if header :contains "Subject" "Spam" {
  discard; # inline comment
}
''';
      final ctx = run(script, _email(subject: 'Spam'));
      expect(ctx.isCancelled, isTrue);
    });

    test('block comments are ignored', () {
      const script = '''
/* block comment */
if /* inline block */ header :contains "Subject" "Spam" {
  discard;
}
''';
      final ctx = run(script, _email(subject: 'Spam'));
      expect(ctx.isCancelled, isTrue);
    });
  });

  group('SieveParser — exists test', () {
    test('exists fires when header is present and non-empty', () {
      const script = '''
if exists "X-Spam-Flag" {
  discard;
}
''';
      const email = SieveEmailContext(
        headers: {
          'x-spam-flag': ['YES'],
        },
      );
      final ctx = interp.execute(parser.parse(script), email);
      expect(ctx.isCancelled, isTrue);
    });
  });

  group('SieveParser — rule model', () {
    test('simple if produces one rule with branchGroupId', () {
      final rules = parser.parse(
        'if header :contains "Subject" "x" { discard; }',
      );
      expect(rules, hasLength(1));
      expect(rules.first.branchGroupId, isNotNull);
      expect(rules.first.conditions, hasLength(1));
      expect(rules.first.actions, hasLength(1));
      expect(rules.first.actions.first, isA<DiscardAction>());
    });

    test('if/elsif produces two rules with the same branchGroupId', () {
      const script = '''
if header :contains "Subject" "a" { discard; }
elsif header :contains "Subject" "b" { keep; }
''';
      final rules = parser.parse(script);
      expect(rules, hasLength(2));
      expect(rules[0].branchGroupId, equals(rules[1].branchGroupId));
      expect(rules[0].isElseBranch, isFalse);
      expect(rules[1].isElseBranch, isFalse);
    });

    test('else rule has isElseBranch=true', () {
      const script = '''
if header :contains "Subject" "a" { discard; }
else { keep; }
''';
      final rules = parser.parse(script);
      expect(rules, hasLength(2));
      expect(rules.last.isElseBranch, isTrue);
    });

    test('HeaderCondition fields are populated', () {
      final rules = parser.parse(
        'if header :contains ["From","Reply-To"] "x@y.com" { keep; }',
      );
      final cond = rules.first.conditions.first as HeaderCondition;
      expect(cond.headers, containsAll(['From', 'Reply-To']));
      expect(cond.matchType, ':contains');
      expect(cond.keyList, contains('x@y.com'));
    });
  });
}
