package System::Introspector::Report::Publish::MediaWiki;
use Moo;
use Try::Tiny;
use Log::Contextual::WarnLogger;
use Log::Contextual qw( :log ),
  -default_logger => Log::Contextual::WarnLogger->new({
    env_prefix => 'SI_REPORT_MEDIAWIKI',
    levels      => [qw( error fatal warn )],
  });

use aliased 'System::Introspector::Report::Publish::MediaWiki::Connection';

has page_options => (
  is => 'ro',
  default => sub { {} },
  init_arg => 'page',
);

has connection => (is => 'ro', lazy => 1, builder => 1, handles => {
  get_page => 'get',
  put_page => 'put',
});

has api_uri         => (is => 'ro', required => 1);
has username        => (is => 'ro', required => 1);
has password        => (is => 'ro', required => 1);
has allow_create    => (is => 'ro', default => sub { 0 });
has http_auth       => (is => 'ro');
has http_realm      => (is => 'ro');

sub _build_producer { Producer->new }

sub _build_connection {
  my ($self) = @_;
  return Connection->new(
    api_uri         => $self->api_uri,
    username        => $self->username,
    password        => $self->password,
    allow_create    => $self->allow_create,
    $self->http_auth ? (
      http_auth  => 1,
      http_realm => $self->http_realm,
    ) : (),
  );
}

sub publish {
  my ($self, $reports) = @_;
  my $pages = $self->page_options;
  log_info {
    sprintf "Pushing reports to MediaWiki at '%s'", $self->api_uri;
  };
  for my $page (sort keys %$pages) {
    $self->_publish_page($reports, $page, $pages->{$page});
  }
  log_info { "Finished pushing to MediaWiki" };
  return 1;
}

sub _sort_reports {
  my ($self, $reports, $included) = @_;
  my @matchers = map {
    $self->_prepare_matcher_from($_);
  } ref($included) ? @{$included} : $included;
  my @grouped;
  for my $report (@$reports) {
    my ($group_idx) = grep {
      $self->_match_id($report, $matchers[$_]);
    } 0 .. $#matchers;
    if (defined $group_idx) {
      push @{$grouped[$group_idx]}, $report;
    }
  }
  return [ map { (@$_) } @grouped ];
}

sub _publish_dynamic {
  my ($self, $reports, $page_name, $key, $options) = @_;
  my %reports_by_key;
  for my $report (@$reports) {
    my $value = $report->{meta}{$key};
    next unless defined $value;
    push @{ $reports_by_key{$value} ||= [] }, $report;
  }
  for my $value (keys %reports_by_key) {
    (my $effective_page = $page_name)
      =~ s{\%\(meta:\Q$key\E\)}{$value};
    my $assoc = $reports_by_key{$value};
    $self->_publish_page($assoc, $effective_page, $options);
  }
  return 1;
}

sub _publish_page {
  my ($self, $reports, $page_name, $options) = @_;
  if ($page_name =~ m{\%\(meta:(.+?)\)}) {
    return $self->_publish_dynamic($reports, $page_name, $1, $options);
  }
  my $sorted = $self->_sort_reports($reports, $options->{report} || []);
  unless (scalar @$sorted) {
    log_debug { "Skipping page '$page_name': No reports to publish" };
    return 1;
  }
  my $do_create = $options->{create};
  return try {
    my $page = $self->get_page($page_name);
    if ($page->is_new and not $do_create) {
      log_trace { "Skipping page '$page_name': Does not yet exist" };
      return 1;
    }
    my $is_changed = $page->update($sorted);
    unless ($is_changed) {
      log_debug { "Not pushing page '$page_name': No changes" };
      return 1;
    }
    log_debug { "Updating page '$page_name'" };
    $self->put_page($page);
    return 1;
  }
  catch {
    log_error { "Error during page update: $_" };
    return 1;
  };
}

with $_ for qw(
  System::Introspector::Report::Publish::API
);

1;
