package LIMS::Model::History;

use Moose;
extends 'LIMS::Model::Base';
with 'LIMS::Model::Roles::SessionData'; # provides $self->user_profile
use namespace::clean -except => 'meta';

use LIMS::Local::Debug;

has edits => (
	is  => 'ro',
	isa => 'ArrayRef[HashRef]',
	default => sub { [] },
	traits => ['Array'],
	handles  => {
        add_to_edits  => 'push',
        have_edits    => 'count',
    },
);

has patient_id => (
    is  => 'rw',
    isa => 'Int',
);

has current_patient => (
    is  => 'rw',
    isa => 'LIMS::DB::Patient',
    lazy_build => 1, # not needed unless have_patient_history = true
);

has patient_history => (
    is  => 'rw',
    isa => 'ArrayRef[LIMS::DB::PatientEdit]',
    default => sub { [] },
	traits => ['Array'],
	handles  => {
        add_to_patient_history => 'push',
        have_patient_history   => 'count',
    },
);

has request_history => (
    is  => 'ro',
    isa => 'ArrayRef',
    default => sub { [] },
	traits => ['Array'],
	handles  => {
        add_to_request_history => 'push',
    },
);

__PACKAGE__->meta->make_immutable;

# ------------------------------------------------------------------------------
sub do_log_action {
    my $self  = shift;
    my $class = shift;
    my $data  = shift; # hashref of (at least) request_id & action
    
    # add user_id to $data:
    $data->{user_id} = $self->user_profile->{id};
    
    my $o = "LIMS::DB::$class";    
    $o->new(%$data)->save;
}

# ------------------------------------------------------------------------------
sub do_log_event {
    my ($self, $args) = @_;
    
    my $request_id = $args->{request_id};
    my $event      = $args->{event};
    
    my %data = (
        request_id => $request_id,
        user_id    => $self->user_profile->{id},
        time       => $self->time_now,        
    );
    
    eval {
        if ($event eq 'view') {
            my $session = $args->{session}; # only required for viewlog
            $data{ip_address} = $session->dataref->{_SESSION_REMOTE_ADDR};
            LIMS::DB::RequestViewLog->new(%data)->save;
        }
        elsif ($event eq 'print') {
            LIMS::DB::RequestPrintLog->new(%data)->save;
        }
    };
    return $@ if $@;
}

# ------------------------------------------------------------------------------
sub get_request_history {
    my ($self, $request_id) = @_;

    my %args = (
        query        => [ request_id => $request_id ],
        with_objects => 'user',
        sort_by      => [ qw(time id) ],
    );

    my $request_history
        = LIMS::DB::RequestHistory::Manager->get_request_histories(%args);

    return $request_history;
}

# ------------------------------------------------------------------------------
sub get_diagnosis_history {
    my ($self, $request_id) = @_;

    my %args = (
        query        => [ request_id => $request_id ],
        with_objects => [ 'user', 'diagnosis', 'option' ],
        sort_by      => 'time',
    );

    my $history = LIMS::DB::RequestDiagnosisHistory::Manager
        ->get_request_diagnosis_histories(%args);

    return $history;
}

# ------------------------------------------------------------------------------
sub get_lab_test_history {
    my ($self, $request_id) = @_;

    my %args = (
        query           => [ request_id => $request_id ],
        require_objects => 'user',
        sort_by         => 'time',
    );

    my $lab_test_history = LIMS::DB::RequestLabTestHistory::Manager
        ->get_request_lab_test_histories(%args);

    return $lab_test_history;
}

# ------------------------------------------------------------------------------
sub get_report_history {
    my ($self, $args) = @_;

    my %args = (
        query => [
            request_id => $args->{request_id},
            field      => $args->{field},
        ],
        require_objects => 'user',
        sort_by         => 'time',
    );

    my $history = LIMS::DB::RequestReportHistory::Manager
        ->get_request_report_histories(%args);
    return $history;
}

# ------------------------------------------------------------------------------
sub get_view_history {
    my ($self, $request_id) = @_;

    my %args = (
        query => [
            request_id => $request_id,
        ],
        require_objects => 'user',
        sort_by         => 'time',
    );

    my $history
        = LIMS::DB::RequestViewLog::Manager->get_request_view_logs(%args);

    return $history;
}

# ------------------------------------------------------------------------------
sub get_print_history {
    my ($self, $request_id) = @_;

    my %args = (
        query => [
            request_id => $request_id,
        ],
        require_objects => 'user',
        sort_by         => 'time',
    );

    my $history
        = LIMS::DB::RequestPrintLog::Manager->get_request_print_logs(%args);

    return $history;
}

# ------------------------------------------------------------------------------
sub get_deletion_history {
    my ($self, $request) = @_; # request object

    my %args = (
        query => [
            request_number => $request->request_number,
            year           => $request->year,
        ],
        require_objects => 'user',
        sort_by         => 'time',
    );

    my $history = LIMS::DB::DeletedRequest::Manager->get_objects(%args);
    return $history;
}

# ------------------------------------------------------------------------------
sub get_deleted_records {
    my ($self, $ref) = @_; # $self->debug($ref);

    my %args = ( require_objects => 'user' );
    # hmds_ref search will collect all rows:
    if ( my $hmds_ref = $ref->{hmds_ref} ) {
        my ($request_number, $yr) = split '/', $hmds_ref;
        $args{query} = [ request_number => $request_number, year => $yr + 2000 ];
        my $o = LIMS::DB::DeletedRequest::Manager->get_objects(%args);
        return $o;
    }
    
    # patient name search will only get 1 row, so need to use hmds ref to get rest:
    for my $param ( qw/first_name last_name/ ) {
        next unless $ref->{$param};
        push @{ $args{query} }, ( action => { rlike => $ref->{$param} } );
    }
    my $o = LIMS::DB::DeletedRequest::Manager->get_objects(%args);
    
    my @data;
    for my $event (@$o) { # in case same patient deleted more than once
        my $request_number = $event->request_number;
        my $yr             = $event->year;
        
        $args{query} = [ request_number => $request_number, year => $yr ];
        my $o = LIMS::DB::DeletedRequest::Manager->get_objects(%args);
        push @data, $_ for @$o; 
    }
    return \@data;    
}

=begin # replaced by requests.created_at col instead:
# ------------------------------------------------------------------------------
sub get_request_histories_for_action {
    my ($self, $action, $request_ids) = @_;

    my $histories = LIMS::DB::RequestHistory::Manager->get_request_histories(
        query => [
            request_id => $request_ids, # arrayref -> 'IN'
            action     => $action,
        ],
    );

    return $histories;
}
=cut

# ------------------------------------------------------------------------------
sub get_demographic_history {
    my $self       = shift;
    my $patient_id = shift; # warn $patient_id;
    
    my @args = (
        require_objects => 'user',
        query => [ patient_id => $patient_id ],
        sort_by => 'time',
    );
    my $history = LIMS::DB::PatientDemographicHistory::Manager
        ->get_patient_demographic_histories(@args);
    return $history;
}

# ------------------------------------------------------------------------------
sub get_patient_history {
    my $self       = shift;
    my $patient_id = shift; # warn $patient_id;

    my %args = (
        query           => [ patient_id => $patient_id ],
        require_objects => [ qw(error_code user) ],
        sort_by         => 'time',
    );

    my $history = LIMS::DB::PatientEdit::Manager
        ->get_patient_edits_iterator(%args);

    while (my $edit = $history->next) {
        $self->add_to_patient_history($edit);
    }

    # no need to proceed if no $patient_history object:
    return 0 unless $self->have_patient_history;

    # grep changes made in each edit, into $self->edits:
    $self->_parse_patient_history_for_changes($patient_id);

    if ($self->have_edits) { # DEBUG $self->edits;
        return $self->edits;
    }
}

# ------------------------------------------------------------------------------
sub _parse_patient_history_for_changes {
    my ($self, $patient_id) = @_;

    # $self->_build_current_patient needs patient.id:
    $self->patient_id($patient_id);

    my $patient_history = $self->patient_history;
    my $current_patient = $self->current_patient;

    # get fields from patients table (except id & timestamps):
    my @patient_demographics = grep {
        $current_patient->meta->column($_)->type !~ /serial|timestamp/;
    } $current_patient->meta->column_names; # $self-debug(@patient_demographics);
    
    my $no_of_entries = @$patient_history; # DEBUG $i;

    {
        no warnings 'uninitialized'; # poss null cols in 'foreach' block

        # cycle through dereferenced $patient_history array:
        for my $i(1 .. $no_of_entries) { # DEBUG $patient_history;
            # archived entry will be at list position $i minus 1:
            my $original = $patient_history->[$i - 1];
            # updated entry will be at list position $i, or if undef, current_patient:
            my $changed = $patient_history->[$i] || $current_patient;

            # now find diffs between original & changed entries:
            foreach my $col( @patient_demographics ) {
                # warn Dumper [$col, $original->$col, $changed->$col];
                my $original_field
                    = $original->meta->column($col)->type eq 'date' && $original->$col
                        ? $original->$col->dmy # check exists before calling dmy method
                        : $original->$col || 'NULL';
                        
                my $changed_field
                    = $changed->meta->column($col)->type eq 'date' && $changed->$col
                        ? $changed->$col->dmy # check exists before calling dmy method
                        : $changed->$col || 'NULL';
                # warn Dumper [$col, $original_field, $changed_field];

                next if $original_field eq $changed_field;

                my %details = (
                    field_name   => $col,
                    changed_from => $original_field,
                    changed_to   => $changed_field,
                    changed_by   => $original->user->username,
                    changed_on   => $original->time,
                    error_code   => $original->error_code->code,
                );

                $self->add_to_edits(\%details);
            }
        }
    }
}

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

    my $current_patient
        = LIMS::DB::Patient->new(id => $self->patient_id)->load;
    return $current_patient;
}

1;
