# (c) copyright 2025, Andrew Bower <andrew@bower.uk>
#
# Module to support testing mcds against the calypso CardDAV server

package Mcds::McdsCalypso;

use warnings;
use strict;

use Cwd;
use File::Path;
use File::Temp;
use File::Touch qw (touch);
use Config::INI::Writer;
use Git::Wrapper;
use Hash::Merge::Simple qw(merge);
use IPC::Run qw(start pump finish timeout reap_nb new_chunker run);
use Authen::Htpasswd;
use Crypt::GeneratePassword qw(word chars);

# Construct options or new
sub with_htpasswd {
  my $dir = shift;
  return {
    'calypso' => {
      'acl' => {
        'type' => 'htpasswd',
        'filename' => Cwd::abs_path("$dir/htpasswd"),
        'encryption' => "sha1",
      },
    }
  }
}

sub new {
  my $class = shift;
  my %_defaults = (
    'dir' => getcwd,
    'collection' => 'private/test',
  );
  my $self = \%_defaults;
  foreach my $opts (@_) {
    $self = merge $self, $opts;
  }
  $self->{'base'} //= "$self->{'dir'}/calypso";
  $self->{'store'} //= "$self->{'base'}/calendars";
  $self->{'calypso_config'} //= "$self->{'base'}/config";
  $self->{'env_setter'} = sub {
    $ENV{'CALYPSO_CONFIG'} = "$self->{'calypso_config'}";
  };
  my $calypso_config = merge {'storage' => {'folder' => $self->{'store'}}}, $self->{'calypso'} // {};
  File::Path::make_path("$self->{base}");
  File::Path::make_path("$self->{store}");
  Config::INI::Writer->write_file($calypso_config, $self->{'calypso_config'});
  _create_store($self, $self->{'collection'});

  # Create htpasswd file if requested
  my $acl = $self->{'calypso'}->{'acl'} // undef;
  if (defined $acl && $acl->{'type'} eq "htpasswd") {
    touch $acl->{'filename'};
    $self->{'htpasswd'} = Authen::Htpasswd->new(
      $acl->{'filename'},
      { encrypt_hash => $acl->{'encryption'} });
    print "Created empty '$acl->{'filename'}' password file:\n";
  }
  return bless $self, $class;
}

sub DESTROY {
  my $self = shift;

  stop_server();
}

sub _create_store {
  my $self = shift;
  my $collection = shift;

  my $collection_dir = "$self->{'store'}/$collection";

  File::Path::make_path("$collection_dir");
  Config::INI::Writer->write_file( { 'collection' => {
     'is-calendar' => 0,
     'is-addressbook' => 1,
    }},
    "$collection_dir/.calypso-collection");

  my $git = Git::Wrapper->new({
    'dir' => "$collection_dir",
    'autoprint' => 1
  });

  $git->init({'b' => 'master'});
  $git->config('user.name', 'autopkgtest user');
  $git->config('user.email', 'autopkgtest@example.com');
  $git->add('.calypso-collection');
  $git->commit({'m' => 'new addressbook'});
}

sub add_vcards {
  my $self = shift;
  my $vcards = shift;
  my @io = (undef, undef, undef);

  foreach my $vcf (@$vcards) {
      run ['calypso', '-g', '-i', $self->{'collection'}, $vcf],
          (map { \$_ } @io),
          init => $self->{'env_setter'},
          new_chunker,
          timeout(10);
      _dumpio("CALYPSO-IMPORT", \@io);
  }
}

sub start_server {
  my $self = shift;

  my @server_cmd = ('calypso', '-H', 'localhost', '-fg');
  my @io = (undef, undef, undef);

  $self->{'io'} = \@io;
  $self->{'server'} = start
    \@server_cmd,
    (map { \$_ } @io),
    init => $self->{'env_setter'},
    new_chunker,
    timeout(10) or die "starting server";
  pump $self->{'server'} until $io[2] =~ /Starting HTTP server/;
  _dumpio("CALYPSO", \@io);
}

sub stop_server {
  my $self = shift;
  my $server = $self->{'server'};
  if (defined($server)) {
    pump $server;
    _dumpio("CALYPSO", $self->{'io'});
    kill_kill $server;
    finish $server;
    reap_nb;
  }
  undef $self->{'server'};
}

sub url {
  my $self = shift;
  return "http://localhost:5233/$self->{'collection'}";
}

sub _dumpio {
  my $source = shift;
  my $io = shift;
  my $out = $$io[1];
  my $err = $$io[2];
  $out =~ s/^(.)/$source-OUT: $1/mg;
  $err =~ s/^(.)/$source-ERR: $1/mg;
  print $out, $err;
  $$io[1] = $$io[2] = '';
}

sub go {
  my $self = shift;
  my $vcards = shift;
  $self->start_server();
  $self->add_vcards($vcards);
  return $self->url();
}

sub create_random_user {
  my $self = shift;

  my $user = Crypt::GeneratePassword::word(3, 10);
  my $password = Crypt::GeneratePassword::chars(8, 12);
  $self->{'htpasswd'}->add_user($user, $password);

  printf("username: %s\npassword: %s\nhtpasswd:\n", $user, $password);
  printf "Current password file:\n";
  system("cat $self->{'calypso'}->{'acl'}->{'filename'}");

  return ($user, $password);
}

1;
