package Pear::LocalLoop::Plugin::Validators;
use Mojo::Base 'Mojolicious::Plugin';

use Email::Valid;
use Geo::UK::Postcode::Regex qw/ is_valid_pc /;
use Scalar::Util qw/ looks_like_number /;
use File::Basename qw/ fileparse /;
use DateTime::Format::Strptime;
use Try::Tiny;

sub register {
  my ( $plugin, $app, $conf ) = @_;

  $app->validator->add_check( email => sub {
    my ( $validation, $name, $email ) = @_;
    return Email::Valid->address( $email ) ? undef : 1;
  });

  $app->validator->add_check( in_resultset => sub {
    my ( $validation, $name, $value, $key, $rs ) = @_;
    return $rs->search({ $key => $value })->count ? undef : 1;
  });

  $app->validator->add_check( not_in_resultset => sub {
    my ( $validation, $name, $value, $key, $rs ) = @_;
    return $rs->search({ $key => $value })->count ? 1 : undef;
  });

  $app->validator->add_check( postcode => sub {
    my ( $validation, $name, $value ) = @_;
    return is_valid_pc( $value ) ? undef : 1;
  });

  $app->validator->add_check( number => sub {
    my ( $validation, $name, $value ) = @_;
    return looks_like_number( $value ) ? undef : 1;
  });

  $app->validator->add_check( gt_num => sub {
    my ( $validation, $name, $value, $check ) = @_;
    return $value > $check ? undef : 1;
  });

  $app->validator->add_check( lt_num => sub {
    my ( $validation, $name, $value, $check ) = @_;
    return $value < $check ? undef : 1;
  });

  $app->validator->add_check( filetype => sub {
    my ( $validation, $name, $value, $filetype ) = @_;
    my ( undef, undef, $extension ) = fileparse $value->filename, qr/\.[^.]*/;
    $extension =~ s/^\.//;
    return $app->types->type($extension) eq $filetype ? undef : 1;
  });

  $app->validator->add_check( is_iso_date => sub {
    my ( $validation, $name, $value ) = @_;
    $value = $app->iso_date_parser->parse_datetime( $value );
    return defined $value ? undef : 1;
  });

  $app->validator->add_check( is_full_iso_datetime => sub {
    my ( $validation, $name, $value ) = @_;
    $value = $app->parse_iso_datetime( $value );
    return defined $value ? undef : 1;
  });

  $app->validator->add_check( is_object => sub {
    my ( $validation, $name, $value ) = @_;
    return ref ( $value ) eq 'HASH' ? undef : 1;
  });

  $app->validator->add_check( in_range => sub {
    my ( $validation, $name, $value, $low, $high ) = @_;
    return $low < $value && $value < $high ? undef : 1;
  });

  $app->helper( validation_error => sub { _validation_error(@_) } );
}

=head2 validation_error

Returns undef if there is no validation error, returns true otherwise - having
set the errors up as required. Renders out the errors as an array, with status
400

=cut

sub _validation_error {
  my ( $c, $sub_name ) = @_;

  my $val_data = $c->validation_data->{ $sub_name };
  return unless defined $val_data;
  my $data = $c->stash->{api_json};

  my @errors = _validate_set( $c, $val_data, $data );

  if ( scalar @errors ) {
    my @sorted_errors = sort @errors;
    $c->render(
      json => {
        success => Mojo::JSON->false,
        errors => \@sorted_errors,
      },
      status => 400,
    );
    return \@errors;
  }

  return;
}

sub _validate_set {
  my ( $c, $val_data, $data, $parent_name ) = @_;

  my @errors;

  # MUST get a raw validation object
  my $validation = $c->app->validator->validation;
  $validation->input( $data );

  for my $val_data_key ( keys %$val_data ) {

    $validation->topic( $val_data_key );

    my $val_set = $val_data->{$val_data_key};

    my $custom_check_prefix = {};

    for my $val_error ( @{$val_set->{validation}} ) {
      my ( $val_validator ) = keys %$val_error;

      unless (
          $validation->validator->checks->{$val_validator}
          || $val_validator =~ /required|optional/
        ) {
        $c->app->log->warn( 'Unknown Validator [' . $val_validator . ']' );
        next;
      }

      if ( my $custom_prefix = $val_error->{ $val_validator }->{ error_prefix } ) {
        $custom_check_prefix->{ $val_validator } = $custom_prefix;
      }
      my $val_args = $val_error->{ $val_validator }->{ args };
      
      $validation->$val_validator(
        ( $val_validator =~ /required|optional/ ? $val_data_key : () ),
        ( defined $val_args ? @$val_args : () )
      );

      # stop bothering checking if failed, validation stops after first failure
      last if $validation->has_error( $val_data_key );
    }

    if ( $validation->has_error( $val_data_key ) ) {
      my ( $check ) = @{ $validation->error( $val_data_key ) };
      my $error_prefix = defined $custom_check_prefix->{ $check }
                       ? $custom_check_prefix->{ $check }
                       : $check;
      my $error_string = join ('_',
        $error_prefix,
        ( defined $parent_name ? $parent_name :  () ),
        $val_data_key,
      );
      push @errors, $error_string;
    } elsif ( defined $val_set->{ children } ) {
      push @errors, _validate_set(
        $c,
        $val_set->{ children },
        $data->{ $val_data_key },
        $val_data_key );
    }
  }

  return @errors;
}

1;
