package LIMS::Local::Search;

use LIMS::Local::Utils ();
use LIMS::Local::Debug;

use DateTime; DateTime->DefaultLocale('en_GB'); # set default locale
use Data::Dumper;

use Moose;
use namespace::clean -except => 'meta';

has query => (
	is  	=> 'ro',
	isa 	=> 'HashRef',
	default => sub { {} },
	traits  => ['Hash'],
	handles => { set_query => 'set' },
);

has user_profile => ( is => 'rw', isa => 'HashRef', required => 1 );
# needs to be writable (_process_sql_simple_search() needs to delete keys):
has form_fields => ( is  => 'ro', isa => 'HashRef', required => 1 );

__PACKAGE__->meta->make_immutable;

#-------------------------------------------------------------------------------
sub generate_query {
    my $self = shift; 

    my $params  = $self->form_fields; # warn Dumper $params;
    my $profile = $self->user_profile; # warn Dumper $profile;
    
    my $dt = sub { LIMS::Local::Utils::to_datetime_using_parsedate(@_) }; # returns 0 if no date
    my @date_fields = qw(day month year);
	my @params_for_deletion = (); # eg date_extend, date_from, date_to, previous_days
	
	# process sql_simple_search params if supplied:
	if ( my $field = $params->{sql_simple_search} ) {
		$self->_process_sql_simple_search($field);
	}
	
	# first deal with date constraints and reformat date fields:
	{ # requests.created_at & constraints upon; don't use eslif's to ensure all 'invalid'
        # params added to @params_for_deletion; successive 'created_at' will clobber any previous:
        if ( grep $params->{$_}, map 'request_'.$_, @date_fields ) {
            # create date as DT object:
            my %dmy = map { $_ => $params->{'request_'.$_} } @date_fields;
            my $date = DateTime->new(%dmy); # warn Dumper $date->ymd;
            
            # if all_before or all_after flags set:
            if ( my $extend_date = $params->{date_extend} ) {
                my $expr = $extend_date eq 'all_after'
                    ? { ge => $date->ymd } # ie <date> 00:00:00
                    : { le => $date->add(days => 1)->ymd }; # ie <date + 1> 00:00:00
                $self->set_query( created_at => $expr );
				push @params_for_deletion, 'date_extend';
            }
            else { # need to create a 'between' to handle timestamp field:
                my @dates = ( $date->ymd, $date->clone->add(days => 1)->ymd );
                $self->set_query( created_at => { ge_le => \@dates } );
            }
			push @params_for_deletion, 'request_'.$_ for @date_fields; 
        }
		# previous number of days:
		if ( my $n = $params->{previous_days} ) {
			my $date = LIMS::Local::Utils::time_now->subtract(days => $n);
			$self->set_query( created_at => { ge => $date->ymd } );
			push @params_for_deletion, 'previous_days';
		} 
		# specific year:
		if ( my $yr = $params->{specific_year} ) {			
			# $self->set_query( year => $yr ); # need to use created_at:
            my @dates = ( &$dt("1/1/$yr"), &$dt("31/12/$yr")->add(days => 1) );
            $self->set_query( created_at => { ge_le => \@dates } );
			push @params_for_deletion, 'specific_year';
		}
		# date_from +/- date_to:
		if ( grep $params->{$_}, qw(date_from date_to) ) { # don't mix with 'date_extend' param
			my $from = &$dt($params->{date_from}) || &$dt('1/1/2000'); # any valid date before start
			my $to =   &$dt($params->{date_to})   || LIMS::Local::Utils::today();
			
			my @dates = ( $from->ymd, $to->add(days => 1)->ymd ); # ie to 00:00:00 on next day
			$self->set_query( created_at => { ge_le => \@dates } );
			push @params_for_deletion, grep $params->{$_}, qw(date_from date_to);
		}
    }
	{ # DoB:
        if ( grep $params->{$_}, map 'dob_'.$_, @date_fields ) {
            # create dob as DT object:
            my %dmy = map { $_ => $params->{'dob_'.$_} } @date_fields; # warn Dumper \%dmy;
            my $dob = DateTime->new(%dmy); # warn Dumper $dob->ymd;

            $self->set_query( dob => $dob );
			push @params_for_deletion, 'dob_'.$_ for @date_fields;            
        }
	}
	# delete redundant entries in %params list:
	delete $params->{$_} for @params_for_deletion; # warn Dumper \@params_for_deletion;	

	while ( my($field_name, $search_term) = each %$params ) {
		next unless $search_term; # skip if field empty
        
        # referrer name search:
        if ( $field_name eq 'referrer_name' ) {
            # converts comma/spaces to single space for GP's & clinicians format:
            $search_term =~ s/[\,\s]+/ /;
			$self->set_query( 'referrers.name' => { like => $search_term . '%' } );
        }        
        # first_name/last_name/name search:
        elsif ( $field_name =~ /name\Z/ ) { # \A(first|last)_name\Z
            # enable 'like' queries on names:
			$self->set_query( $field_name => { like => $search_term . '%' } );
        }
        # lab_number search:
        elsif ( $field_name eq 'lab_number' ) {
			my ($request_number, $year)
				= LIMS::Local::Utils::split_labno( $search_term );
			$self->set_query( request_number => $request_number );
			$self->set_query( year => $year );
        }
        # nhs_number search:
        elsif ( $field_name eq 'nhs_number' ) {
            # collapse spaces if submitted in nnn nnn nnnn format:
            $search_term =~ s/\s+//g;
			$self->set_query( nhs_number => $search_term );
        }
		# request options:
        elsif ( grep $field_name eq $_, qw(private urgent copy_to) ) {
            $self->set_query( option_name => $field_name );
        }
        # wildcard search:
        elsif ( $search_term =~ /[\%\*]/ ) {
            # substitute * for sql wildcard:
            $search_term =~ s/\*/\%/;
			$self->set_query( $field_name => { like => $search_term } );
        }
        # everything else:
        else {
			$self->set_query( $field_name => $search_term );
        }
    }
    
    # if user is external to local network, are they restricted to own records?
    unless ($profile->{is_local_network}) { # most are, so don't open $cfg unless
        my $cfg = LIMS::Local::Config->instance;
        
        if ($cfg->{settings}->{local_network_restriction}) { # if users restricted: 
            my $parent_code = $profile->{user_location}->{region_code}; # warn 'HERE';
            # only include records from users parent organisation:
            $self->set_query( parent_code => $parent_code ); 
        }
    }

	return $self->query;
}

#-------------------------------------------------------------------------------
sub _process_sql_simple_search {
    my ($self, $search_field) = @_;
	
    my $params = $self->form_fields;
	
	my $logic = $params->{logic}; # MATCHES, CONTAINS, LACKS, etc
	my $search_term = $params->{kwd};

=begin # TODO - could allow AND / OR in search term (need to ensure rels load for AND):
foo => { like => [ '%bar%', '%baz%' ] }, # foo LIKE '%bar%' OR foo LIKE '%baz% 
and => [ foo => { like => 'bar' }, foo => { like => 'baz'} ], # foo LIKE '%bar%' AND foo LIKE '%baz% 
=cut

	my $constraints_map = {
		BEGINS   => { like => $search_term . '%' },
		ENDS     => { like => '%' . $search_term },
		CONTAINS => { like => '%' . $search_term . '%' },
		LACKS    => { 'not like' => '%' . $search_term . '%' },
		MATCHES  => $search_term,
	};	
	
	# get constraint expression for required logic:	
	my $query_constraint = $constraints_map->{$logic};

	$self->set_query( $search_field => $query_constraint );

	# delete these keys so generate_query() doesn't choke on them:
	map { delete $params->{$_} } qw(sql_simple_search logic kwd);
}

1;