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

package Amavis::In::AMPDPTest;

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

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

use Amavis::Out::EditHeader;
use Amavis::Util qw(proto_encode);

sub class { 'Amavis::In::AMPDP' }

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

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';
}

package Amavis::In::AMPDPTest::Hdr_Edits_Response::Msginfo {
  sub new {
    my($class, %args) = @_;
    $args{headers} = {
      map {lc $_ => $args{headers}{$_}} keys %{$args{headers} // {}}
    };
    bless {%args}, $class;
  }
  sub get_header_field_body {
    my($self, $field_name, $j) = @_;
    $self->{headers}{lc $field_name};
  }
  sub AUTOLOAD {
    our $AUTOLOAD;
    shift->{$AUTOLOAD =~ s/.*:://r};
  }
}

sub hdr_edits_response : Tests(8) {
  my $test = shift;
  my $edits = Amavis::Out::EditHeader->new;
  isa_ok $edits, 'Amavis::Out::EditHeader';

  $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 = Amavis::Out::EditHeader->new;
  $edits_inherited->inherit_header_edits($edits);

  my $Long1000 = 'L' x 1000;
  my %headers = (
    H1       => " B1\n",
    H2       => " B2\n",
    H3       => " B3\n",
    Received => " R1\n",
    H4       => " B4\n",
    H5       => " B5\n",
    Long     => " ${Long1000}\n",
  );

  my $H9_998 = do {
    my $preamble = "H9 edited, H9 edited \n";
    sprintf '%s again%s...', $preamble, ('!' x (995 - length ' again'));
  };
  # response line order differs from write_header's output order:
  # delheader/chgheader must precede insheader/addheader, so that their
  # per-name occurrence indices refer to the original header fields and
  # cannot hit a freshly inserted same-named field
  my @expect = (
    proto_encode('chgheader','1','h1',"H1 edited, H1 edited \n again"),
    proto_encode('delheader','1','h2'),
    proto_encode('delheader','1','h5'),
    proto_encode('addheader','H9',$H9_998),
    proto_encode('insheader','0','Add2','Added 2'),
    proto_encode('insheader','0','Add1','Added 1'),
    proto_encode('insheader','0','Prepend1','Prepended 1'),
    proto_encode('insheader','0','Prepend2','Prepended 2'),
    proto_encode('addheader','Append1','Appended 1'),
    proto_encode('addheader','Append2','Appended 2'),
  );

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

  my $expect_log = Set::Scalar->new(
    'INFO: removed bare CR from 2 header line(s)',
    '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 %edits = (
    'original'  => $edits,
    'inherited' => $edits_inherited,
  );

  while (my ($edits_name, $edits) = each %edits) {
    my $msginfo = Amavis::In::AMPDPTest::Hdr_Edits_Response::Msginfo->new(
      header_edits => $edits,
      headers      => \%headers,
    );
    $log->clear;
    my @response = Amavis::In::AMPDP::hdr_edits_response($msginfo);
    my $description = "(${edits_name} edits)";
    eq_or_diff \@response, \@expect, "response lines $description";
    ok $expect_log->is_subset($log), "expected logs found $description";
    ok List::Util::none(sub {/BUG:/}, @$log), "unexpected logs not found $description";
  }

  eq_or_diff [
    Amavis::In::AMPDP::hdr_edits_response(
      Amavis::In::AMPDPTest::Hdr_Edits_Response::Msginfo->new
    )
  ], [], 'no response lines without header edits';
}

1;
