# SPDX-License-Identifier: GPL-2.0-or-later

package Amavis::Out::EditHeaderTest;

use Test::MockModule;
use Test::Most;
use base 'Test::Class';

use IO::Scalar;
use List::Util;
use Set::Scalar;

sub class { 'Amavis::Out::EditHeader' }

sub startup : Tests(startup => 1) {
  my $test = shift;
  use_ok $test->class, qw(hdr);
}

sub constructor : Tests(3) {
  my $test  = shift;
  my $class = $test->class;
  can_ok $class, 'new';
  ok my $lookup = $class->new,
  '... and the constructor should succeed';
  isa_ok $lookup, $class, '... and the object it returns';
}

sub unfold_hdr {
  # strip eol nl as well
  hdr(@_) =~ s/\n(?=[ \t]|$)//gsr;
}

sub folding : Tests(8) {
  my $test  = shift;

  for my $wsp (' ', "\t") {

    my $raw = "0000${wsp}00000" x 10;

    is
      unfold_hdr(Subject => $raw, 0, undef, 0),
      'Subject: ' . $raw,
      'structured == 0: hdr is reversible';

    is
      unfold_hdr(Subject => $raw, 1, undef, 0),
      'Subject: ' . $raw,
      'structured == 1: hdr does not fold';

    is
      unfold_hdr(Subject => $raw, 2, undef, 0),
      'Subject: ' . $raw,
      'structured == 2: hdr does not fold';
  }

  unlike
    unfold_hdr(Subject => "0000\n00000" x 10, 1, undef, 0),
    qr/\n(?![\t ]|$)/,
    'structured == 1: hdr does not return multiple lines that could be mistaken for multiple headers';

  unlike
    unfold_hdr(Subject => "0000\n00000" x 10, 2, undef, 0),
    qr/\n(?![\t ]|$)/,
    'structured == 2: hdr does not return multiple lines that could be mistaken for multiple headers';
}

package Amavis::Out::EditHeaderTest::Write_Header::Msginfo {
    sub new {
        my $class = shift;
        bless {@_}, $class;
    }
    sub AUTOLOAD {
        our $AUTOLOAD;
        shift->{$AUTOLOAD =~ s/.*:://r};
    }
}

sub write_header : Tests(13) {
    my $test = shift;
    my $edits = $test->class->new;
    isa_ok $edits, $test->class;

    $edits->add_header('Add1' => 'Added 1');
    $edits->add_header('Add2' => 'Added 2');
    $edits->append_header('Append1' => 'Appended 1');
    $edits->append_header('Append2' => 'Appended 2');
    $edits->delete_header('H2');
    $edits->delete_header('H5');
    $edits->edit_header('H1' => sub {'H1 edited'});
    $edits->edit_header('H1' => sub {$_[1] . ", H1\r edited\n \n again"});
    $edits->prepend_header('Prepend1' => 'Prepended 1');
    $edits->prepend_header('Prepend2' => 'Prepended 2');

    $edits->delete_header('Add2');
    $edits->delete_header('Append2');
    $edits->delete_header('Prepend2');

    # Without effect if H8 does not exist
    $edits->edit_header('H8' => sub {die $_[1] . 'H8 edited'});
    $edits->edit_header('H8' => sub {die $_[1] . ", H8\r edited\n \n again" . '!' x 1000});
    # Should be upserted
    $edits->upsert_header('H9' => sub {($_[1] // '') . 'H9 edited'});
    $edits->upsert_header('H9' => sub {($_[1] // ''). ", H9\r edited\n \n again" . '!' x 1000});

    my $edits_inherited = $test->class->new;
    $edits_inherited->inherit_header_edits($edits);

    my $Long1000 = sprintf 'Long: %s', 'L' x 1000;
    my $Long998  = sprintf 'Long: %s...', 'L' x (995 - length('Long: '));
    my $mail_text_str = \<<~EOD;
        H1: B1
        H2: B2
        H3: B3
        Received: R1
        H4: B4
        H5: B5
        ${Long1000}

        My body Lies over the Ocean
        EOD

    my $H9_998 = do {
        my $preamble = "H9: H9 edited, H9 edited \n";
        sprintf '%s again%s...', $preamble, ('!' x (995 - length ' again'));
    };
    my $expect = <<~EOD . "\n";
        Prepend2: Prepended 2
        Prepend1: Prepended 1
        Add1: Added 1
        Add2: Added 2
        H1: H1 edited, H1 edited 
         again
        H3: B3
        Received: R1
        H4: B4
        ${Long998}
        ${H9_998}
        Append1: Appended 1
        Append2: Appended 2
        EOD

    my $log = Set::Scalar->new;
    my $mock = Test::MockModule->new($test->class)->redefine(
        do_log => sub ($$;@) {
            $log->insert(sprintf($_[1], @_[2..$#_]));
        }
    );

    my $expect_log = Set::Scalar->new(
        'INFO: removed bare CR from 2 header line(s)',
        'INFO: truncated 1 header line(s) longer than 998 characters',
        'INFO: unfolded 1 illegal all-whitespace continuation lines',
        sprintf(
            'INFO: truncating long header field (len=1033): ' .
            "H9: H9 edited, H9\r edited \n again%s[...]" ,
            '!' x 67),
    );

    my %tests = (
        'with header_in_array'    => Amavis::Out::EditHeaderTest::Write_Header::Msginfo->new(
            skip_bytes    => 0,
            mail_text_str => $mail_text_str,
        ),
        'without header_in_array' => Amavis::Out::EditHeaderTest::Write_Header::Msginfo->new(
            skip_bytes => 0,
            mail_text  => IO::Scalar->new($mail_text_str),
        ),
    );

    my %edits = (
        'original'  => $edits,
        'inherited' => $edits_inherited,
    );

    while (my ($edits_name, $edits) = each %edits) {
        while (my ($test_name, $msginfo) = each %tests) {
            my $out;
            $log->clear;
            $edits->write_header($msginfo, IO::Scalar->new(\$out), 1);
            my $description = "${test_name} (${edits_name} edits)";
            is $out, $expect, "write $description";
            ok $expect_log->is_subset($log), "expected logs found $description";
            ok List::Util::none(sub {/BUG:/}, @$log), "unexpected logs not found $description";
        }
    }
}

1;
