package LIMS::Model::Roles::ResultsUpdate;

use Moose::Role;

# has history_params => ( is => 'ro', isa => 'HashRef', lazy_build => 1 ); # needs to handle loops
has request_status_options_map => ( is => 'ro', isa => 'HashRef', lazy_build => 1 );

use Data::Dumper;

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

    my $user_profile_id = $self->user_profile->{id};
    my $history_params  = $self->history_params; # warn Dumper $history_params;
    my $form_data       = $self->form_data; # warn Dumper $form_data;

    # get current result_summary for this request & section:
    my $request_results_summary = $self->_get_results_summary();
        # warn Dumper $request_results_summary->as_tree;

    # 'results_summary' is textbox, '_results_summary' is drop_down
    # join textbox entry (if exists) onto end of drop-down menu selection (if exists):
    my $form_input = join "\n", map $form_data->{$_}, grep $form_data->{$_},
        qw( _results_summary results_summary ); # warn $form_input;

    # apply any results_summary expansion:
    if ( my $results_summary = $form_data->{_results_summary} ) { # drop-down, not free text
        my %h = (
            section_id  => $form_data->{_section_id},
            summary_txt => $results_summary,
        );
        if ( my $text = $self->_get_results_summary_expansion(\%h) ) {
            # append expansion text unless already applied (only possible if
            # drop-down incorrectly re-submitted in combination with free-text):
            $form_input .= '. ' . $text unless $form_input =~ /$text/;
        }
    } # warn $form_input;

    my $results_summary = ''; # maybe set below

    # return if results_summary exists & no change:
    if  ($results_summary = $request_results_summary->results_summary) {
        no warnings 'uninitialized';
        return if $results_summary eq $form_input;
    }

    # if existing results_summary, and/or form submission containing results_summary:
    if ( $results_summary || $form_input ) {
       my $section_name = $form_data->{_section_name};

        # if $form_input, do insert/update, else delete:
        if ($form_input) {
            # if results_summary already exists, update, else new:
            my $is_new = ( not $request_results_summary->results_summary );

            # update result_summary:
            $request_results_summary->results_summary($form_input);

            # add user_id:
            $request_results_summary->user_id($user_profile_id);

=begin # insert_or_update() preserves original timestamp on update:
UPDATE r_s_s SET results_summary = ?, time = ? WHERE request_id = ? AND lab_section_id = ?
bind params: "<rs>", '2014-11-24 16:08:29', 241124, 4 # time = original insert timestamp
=cut
# ========= fix for failure to update timestamp ================================
            $request_results_summary->time(undef) unless $is_new; # forces update
# ==============================================================================
            $request_results_summary->insert_or_update;
            # $request_results_summary->save(changes_only => 1); # might also work

            # maybe auto-request new tests if new drop-down result_summary:
            if ($is_new) { # warn Dumper $form_data;
				# if results summary contains drop-down entry:
				if ( my $str = $form_data->{_results_summary} ) {
                    my @args = (
                        query           => [ description => $str ],
                        require_objects => 'result_summary',
                    );
                    my $o = LIMS::DB::ResultSummaryLabTest::Manager
                        ->get_objects(@args);
                    if (@$o) { # warn Dumper $_->as_tree for @$o;
                        my @test_ids = map $_->lab_test_id, @$o;
                        my %h = (
                            test_ids => \@test_ids,
                            action   => 'results summary',
                        ); # warn Dumper \%h;
                        $self->auto_request_additional_tests(\%h);
                    }
				}
            }
			{
				my $action = $is_new ? 'new ' : 'updated ';
				$action .= ( $section_name . ' result summary' );
			    $history_params->{action} = $action; # warn $action;
			}
            # TODO: equivalent of report add_to_actions() for request_lab_test_history
            LIMS::DB::RequestLabTestHistory->new(%$history_params)->save;
        }
        else { # delete:
            $request_results_summary->delete;
            $history_params->{action} = "deleted $section_name result summary";
            LIMS::DB::RequestLabTestHistory->new(%$history_params)->save;
        }
    }
}

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

    my $history_params = $self->history_params;
    my $form_data      = $self->form_data; # $self->debug($form_data);

    # get current notes for this request & section:
    my $request_section_notes = $self->_get_section_notes();

    # return if no difference:
    if ($request_section_notes) {
        no warnings 'uninitialized';
        return if $request_section_notes->details eq $form_data->{section_notes};
    }

    # if existing section notes, and/or form submission containing section notes:
    if ( $request_section_notes || $form_data->{section_notes} ) {
        my %args = (
            request_id     => $form_data->{_request_id},
            lab_section_id => $form_data->{_section_id},
        );
        my $o = LIMS::DB::RequestLabSectionNote->new(%args);

        my $section_name = $form_data->{_section_name};

        # if $form_input, do insert/update, else delete:
        if (my $form_input = $form_data->{section_notes}) {
            $o->details($form_input);
            $o->insert_or_update;

            $history_params->{action} = $request_section_notes ?
                "updated $section_name section notes"
                    : "new $section_name section notes";

            LIMS::DB::RequestLabTestHistory->new(%$history_params)->save;
        }
        else {
            $o->delete;
            $history_params->{action} = "deleted $section_name section notes";
            LIMS::DB::RequestLabTestHistory->new(%$history_params)->save;
        }
    }
}

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

	my $history_params = $self->history_params;
	my $form_data      = $self->form_data; # $self->debug($form_data);

	# get current foreign id for this request & section:
	my $remote_system_id = $self->_get_remote_system_id();

	# return if no difference:
	if ($remote_system_id) {
		no warnings 'uninitialized';
		return if $remote_system_id->foreign_id eq $form_data->{foreign_id};
	}

	# if existing remote_system_id, and/or form submission containing foreign id:
	if ( $remote_system_id || $form_data->{foreign_id} ) {
		my %args = (
			lab_section_id => $form_data->{_section_id},
			request_id     => $form_data->{_request_id},
		);

		my $o = LIMS::DB::RequestLabSectionForeignID->new(%args);

		my $section_name = $form_data->{_section_name};

		# if $form_input, do insert/update, else delete:
		if ( my $form_input = $form_data->{foreign_id} ) {
			$o->foreign_id($form_input);
			$o->insert_or_update;

			$history_params->{action} = $remote_system_id
				? "updated $section_name remote system ID"
				: "new $section_name remote system ID";

			LIMS::DB::RequestLabTestHistory->new(%$history_params)->save;
		}
		else {
			$o->delete;
			$history_params->{action} = "deleted $section_name remote system ID";

			LIMS::DB::RequestLabTestHistory->new(%$history_params)->save;
		}
	}
}

#-------------------------------------------------------------------------------
# request new lab_test or lab_investigation (uses same form param 'test_id'):
sub do_new_lab_investigation {
    my ($self, $test_id) = @_;

    my $history_params = $self->history_params;

    my $test = LIMS::DB::LabTest->new(id => $test_id)->load;

    my $status_option_new
        = LIMS::DB::LabTestStatusOption->new(description => 'new')->load;

    my $user_profile_id = $self->user_profile->{id};
    my $request_id      = $self->form_data->{_request_id};

    my %params = (
        lab_test_id => $test_id,
        request_id  => $request_id,
    );

    # create new request_lab_test object:
    my $request_lab_test = LIMS::DB::RequestLabTestStatus->new(%params);

    my $action = '';
    # load row (if exists):
    if ( $request_lab_test->load(speculative => 1) ) {
        # return if lab_test status already 'new' (won't get linked lab tests below):
        return if $request_lab_test->status_option_id == $status_option_new->id;
        $action = 're-requested';
    }
    else {
        $action = 'requested';
    }

    # update status_option_id & user_id:
    $request_lab_test->status_option_id($status_option_new->id);
    $request_lab_test->user_id($user_profile_id);

    # insert or update object, update history on success:
    if ( $request_lab_test->save(changes_only => 1) ) {
        $history_params->{action} = $action . ' ' . $test->field_label;
        LIMS::DB::RequestLabTestHistory->new(%$history_params)->save;
    }

    { # add linked tests if configured:
        my $linked_tests_map = $self->linked_lab_tests; # M::R::LabTestUpdate
        # get lab_test_map - M::R::LabTestUpdate
        my $lab_tests_map = $self->lab_test_map(); # warn Dumper $lab_tests_map;

        if ( my $ref = $linked_tests_map->{$test_id} ) { # arrayref of test.id's
            $self->do_linked_lab_tests($ref, $request_id); # M::R::LabTestUpdate
            # log action in request_lab_test_history:
            if ( $self->have_requested_lab_tests ) { # set in do_new_lab_test() on save
                for (@$ref) {
                    my $field_label = $lab_tests_map->{$_}; # warn $field_label;
                    $history_params->{action}
                        = "requested linked test $field_label";
                    LIMS::DB::RequestLabTestHistory->new(%$history_params)->save;
                }
                $self->reset_requested_lab_tests; # clear before saving next lab-test
            }
        }
    }
}

#-------------------------------------------------------------------------------
# udpates request_lab_test_status to complete if config = status_complete_on_result_entry
# (genomics) and status not already set to complete:
sub do_complete_selected_tests {
    my ($self, $lab_test_ids) = @_; # warn Dumper $lab_test_ids;

    my $history_params = $self->history_params;

    my $status_option
        = LIMS::DB::LabTestStatusOption->new(description => 'complete')->load;

    for my $id (@$lab_test_ids) {
        my $n = LIMS::DB::RequestLabTestStatus::Manager
            ->update_request_lab_test_status(
                set => {
                    user_id          => $self->user_profile->{id},
                    status_option_id => $status_option->id,
                },
                where => [
                    request_id  => $self->form_data->{_request_id},
                    lab_test_id => $id,
                    status_option_id => { ne => $status_option->id }
                ],
            ); # warn Dumper $n;
        if ($n) { # only if status changed to complete above:
            my $test_name = LIMS::DB::LabTest->new(id => $id)->load->field_label;
            $history_params->{action} = "auto-set $test_name status to complete";
            LIMS::DB::RequestLabTestHistory->new(%$history_params)->save;
        }
    }
}

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

    my $history_params = $self->history_params;
    my $form_data      = $self->form_data; # $self->debug($form_data);

    my $status_option
        = LIMS::DB::LabTestStatusOption->new(description => 'complete')->load;

    # can't use require_objects in update_products, so have to do it in 2 stages:
    my %args = (
        query => [ lab_section_id => $form_data->{_section_id} ],
    );
    my $section_lab_tests = LIMS::DB::LabTest::Manager->get_lab_tests(%args);
    my @lab_test_ids = map $_->id, @$section_lab_tests;

    my $num_rows_updated = LIMS::DB::RequestLabTestStatus::Manager
        ->update_request_lab_test_status(
            set => {
                user_id          => $self->user_profile->{id},
                status_option_id => $status_option->id,
            },
            where => [
                request_id  => $self->form_data->{_request_id},
                lab_test_id => \@lab_test_ids,
                status_option_id => { ne => $status_option->id }
            ],
        ); # warn $num_rows_updated;

    $history_params->{action}
        = "set $form_data->{_section_name} section tests status to complete";
    LIMS::DB::RequestLabTestHistory->new(%$history_params)->save;
}

#-------------------------------------------------------------------------------
# revert request status from complete to reported/authorised:
sub do_request_status_check {
    my $self = shift;

    my $request_id = $self->form_data->{_request_id};

    # get map of request status options:
    my $status_option = $self->request_status_options_map; # warn Dumper $status_option;

    my $request = LIMS::DB::Request->new(id => $request_id)->load;

    # only interested if request status is complete:
    return if $request->status_option_id != $status_option->{complete}->{id};

    # revert request status to reported/authorised:
    my $new_status = $status_option->{authorised}->{is_active} eq 'yes'
        ? $status_option->{authorised}->{id}
        : $status_option->{reported}->{id};

    $request->status_option_id($new_status);
    $request->save;
}

#-------------------------------------------------------------------------------
sub history_params { # was a lazy_build, but now needs to cope with loops
    my $self = shift;

    my %params = (
        request_id => $self->form_data->{_request_id},
        user_id    => $self->user_profile->{id},
    );

    return \%params;
}

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

	my $o = LIMS::DB::StatusOption::Manager->get_status_options;

	my %map = map {
		$_->description => $_->as_tree;
	} @$o;

	return \%map;
}

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

    my %args = (
        request_id => $self->form_data->{_request_id},
        section_id => $self->form_data->{_section_id},
    );
	# use existing M::Result method:
    my $results_summary = $self->get_request_results_summary(\%args);
	return $results_summary;
}

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

    my @args = @{$args}{qw/summary_txt section_id/}; # warn Dumper \@args;

    my $dbix = $self->lims_dbix; # RDBO uses left join, need inner join:
    my $sql  = q!SELECT t2.description
        FROM result_summary_options t1
            JOIN result_summary_expansion t2
                ON t1.id = t2.result_summary_option_id
        WHERE t1.description = ? and t1.lab_section_id = ?
            and t2.is_active = 'yes'!;

    my $row = $dbix->query($sql, @args)->list; # warn $row;
    return $row;
}

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

    my %args = (
        request_id     => $self->form_data->{_request_id},
        lab_section_id => $self->form_data->{_section_id},
    );

    my $section_notes
        = LIMS::DB::RequestLabSectionNote->new(%args)->load(speculative => 1);

    return $section_notes;
}

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

	my %args = (
		lab_section_id => $self->form_data->{_section_id},
		request_id     => $self->form_data->{_request_id},
    );

	my $remote_system_id
		= LIMS::DB::RequestLabSectionForeignID->new(%args)->load(speculative => 1);

	return $remote_system_id;
}

1;
