package LIMS::Model::Patient;

use Moose;
extends 'LIMS::Model::Base';
with 'LIMS::Model::Roles::SessionData'; # provides $self->user_profile
has error_codes_map => ( is => 'ro', isa => 'HashRef', lazy_build => 1 );
use namespace::clean -except => 'meta';

__PACKAGE__->meta->make_immutable;

use Rose::DB::Object::Util qw(:columns);

#-------------------------------------------------------------------------------
# TODO: updating record with new data if matching nhs_number (unique index) - should reject
# now dies if empty nhs_number: "Cannot load LIMS::DB::Patient without a primary
# key (id) with a non-null value or another unique key with at least one non-null value."

sub create_new_patient { 
    my $self = shift;
    my $data = shift; # $self->debug($data); return;

    my $patient_case = LIMS::DB::PatientCase->new;
    $self->_update_object_with_data({ object => $patient_case, data => $data });

    my $patient = LIMS::DB::Patient->new;
    $self->_update_object_with_data({ object => $patient, data => $data });

	# add created_at time:
	$patient->created_at($self->time_now);

=begin # causes error if nhs_number empty:
    # add patient object to patient_case object:
    $patient_case->patient($patient);

    # combined save:
    $patient_case->save; # warn $patient_case->id;
=cut

	my $db = $self->lims_db; # ie LIMS::DB->new_or_cached;
	
#$self->set_rose_debug(1);
	$db->do_transaction( sub {		
		$patient->save;
		
		my $patient_id = $patient->id
			|| die 'cannot retrieve patient_id in create_new_patient()';

		$patient_case->patient_id($patient_id);

		$patient_case->save;
	});
#$self->set_rose_debug(0);
	# warn [ $patient->id, $patient_case->id ];

	return $patient_case->id;
}

#-------------------------------------------------------------------------------
sub merge_patients {
	my $self = shift;
	my $data = shift; # $self->debug($data); 
	
	# patient_case_id's, 'from' & 'to':
	my $from_id = $data->{from};    # arrayref (1 or more) - keep
	my ($to_id) = @{ $data->{to} }; # arrayref (1 item only) - deref

	my $db = $self->lims_db;
	
	# get list of all patient_cases having patient_id in 'from' patient_cases:
	my $patient_cases_from = $self->_get_patient_cases_from($from_id);

	# get data for 'to' patient_case:
	my $case_to = LIMS::DB::PatientCase->new(id => $to_id)->load;
	
	# find existing combination of new patient_id + old referral_source &
	# unit_number, or create new:
	my $merge = sub {
		foreach my $old_patient_case (@$patient_cases_from) { # $self->debug($_)
			# clone object so we don't clobber old vals before archiving:		
			my $new_patient_case = $old_patient_case->clone;
			
			# replace patient_id with new 'to' value:
			$new_patient_case->patient_id($case_to->patient_id);
			
			# replace unit_number with new 'to' value if required:
			if ($data->{scope} eq 'unit_no') {
				$new_patient_case->unit_number($case_to->unit_number);
			}
			# remove patient_case PK (id) or get row re-loaded on load_or_insert():
			$new_patient_case->id(undef); 
	
			# retrieve existing or create new:
			$new_patient_case->load_or_insert(); # warn $patient_case->id;
			
			# update requests table with new patient_case_id:
			my $i = LIMS::DB::Request::Manager->update_requests(
				set   => { patient_case_id => $new_patient_case->id },
				where => [ patient_case_id => $old_patient_case->id ],
			);
		}
		# can now delete old patient_cases + patients:
		$self->_archive_and_delete_patients($patient_cases_from, $case_to);	
	};
	
	my $ok = $db->do_transaction($merge);
	
	# don't need return value unless error:
    return $ok ? 0 : 'merge_patients() error - ' . $db->error;
}

#-------------------------------------------------------------------------------
sub get_patient {
	my $self = shift;
	my $patient_id = shift;

	my $patient = LIMS::DB::Patient->new(id => $patient_id)->load;

	return $patient;
}

#-------------------------------------------------------------------------------
# used only by Merge::do_merge - can delete when changed to patient_case
sub get_patients {
	my ($self, $patient_ids) = @_; # arrayref

	my $patients
		= LIMS::DB::Patient::Manager->get_patients(query => [ id => $patient_ids ]);

	return $patients;
}

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

	my $o = LIMS::DB::Request->new(id => $request_id)
		->load(with => 'patient_case.patient');
	
	return $o->patient_case->patient;
}

#-------------------------------------------------------------------------------
sub get_similar_patients {
	my ($self, $patient) = @_;
	
	my %args = (
		query => [
			last_name    => $patient->last_name,
			first_name   => $patient->first_name,
			'patient.id' => { ne => $patient->id },
		],
		require_objects => [ qw(patient referral_source) ],
	);
#$self->set_rose_debug(1);	
	my $patients = LIMS::DB::PatientCase::Manager->get_patient_cases(%args);
#$self->set_rose_debug(0);	
	
	return $patients;
}

#-------------------------------------------------------------------------------
sub check_patient_nhs_number_count {
	my ($self, $nhs_number) = @_; 

	my %args = (
		query => [ nhs_number => $nhs_number ],	
	);
	
	my $count
		= LIMS::DB::Patient::Manager->get_patients_count(%args);

	return $count;
}

#-------------------------------------------------------------------------------
sub update_patient {
    my $self = shift; 
    my $data = shift; # $self->debug($data); # hashref of form data

	# unlikely, but best to check:
	$data->{id} || die 'cannot retrieve patient_id in update_patient()';

    my $db = $self->lims_db; # ie LIMS::DB->new_or_cached;
	
	my $i = 0; # successful updates counter	

	my $update_patient = sub {
		# load existing data for patient:
		my $patient = LIMS::DB::Patient->new(id => $data->{id})->load;

		# freeze patient data in archive:
		my $archive = $self->_create_patient_archive($patient);
		# add error_code_id:
		$archive->error_code_id($data->{error_code_id});

		# patient_id might be submitted by 'use this' radio button:
		if ( my $new_patient_id = $data->{use_patient_id} ) {
			$i = $self->_update_requests_with_new_patient_case($data);
			# need to set patient_id in $archive to new 'use_patient_id' value:
			$archive->patient_id($new_patient_id);
		}
		else {
			# update $patient object with new form data:
			$self->_update_object_with_data({ object => $patient, data => $data });
			
			return unless $patient->dirty_columns; # warn 'have dirty_cols';

			$i = $patient->save(changes_only => 1) ? 1 : 0;	# $i gets memory address if scalar ?		
		}
		
		$archive->save;
	};
	
#$self->set_rose_debug(1);
	$db->do_transaction( $update_patient );
#$self->set_rose_debug(0);

	# return hashref of db error (if any), and numerical value of success (updates count):
	return {
		error   => $db->error ? 'update_patient() error - ' . $db->error : undef,
		success => $i,
	}
}

#-------------------------------------------------------------------------------
sub _update_requests_with_new_patient_case { 
	my $self = shift;
	my $data = shift; # $self->debug($data); # form data
	
	my $old_patient_id = $data->{id};
	my $new_patient_id = $data->{use_patient_id};
	
	# get all patient_cases using old patient id:
	my $cases = LIMS::DB::PatientCase::Manager
		->get_patient_cases( query => [ patient_id => $old_patient_id ] );
	
	my $i;
	
	foreach my $case (@$cases) {
		# look for existing combination of existing ref_src_id/unit_no & new pat.id:
		my %args = (
			referral_source_id => $case->referral_source_id,
			unit_number 	   => $case->unit_number,
			patient_id 		   => $new_patient_id,
		);
		
		# load or create new patient_case:
		my $new_patient_case = LIMS::DB::PatientCase->new(%args)->load_or_insert;
		
		{ # update requests with new patient_case.id:
			my %args = (
				set   => { patient_case_id => $new_patient_case->id },
				where => [ patient_case_id => $case->id ],
			);
			$i = LIMS::DB::Request::Manager->update_requests(%args);
		}
	}
	
	{ # update dependent tables (patient_edits, patient_trial)
		my %args = (
			new_patient_id => $new_patient_id,
			old_patient_id => $old_patient_id,
		);
		
		$self->_update_dependent_tables(\%args);
	}

	# delete old patient_id from all tables:
	LIMS::DB::Patient->new(id => $old_patient_id)->delete(cascade => 1); # replaces need for:		
	# LIMS::DB::PatientCase->new(id => $old_patient_case_id, db => $db)->delete;
	# LIMS::DB::Patient->new(id => $old_patient_id, db => $db)->delete;

	return $i;
}

=begin #-------------------------------------------------------------------------------
sub _update_requests_with_new_patient_case { 
	my $self = shift;
	my $data = shift; # $self->debug($data); # form data
	
	# WRONG!!! - _record_id is patient.id !!!!!
	my $request = LIMS::DB::Request->new(id => $data->{_record_id})
		->load(with => 'patient_case');

	my ($i, $patient_case);
	
	# look for existing patient_case or create new:
	{
		my $new_patient_id = $data->{use_patient_id};
		
		my %args = (
			patient_id => $new_patient_id,
			referral_source_id => $request->patient_case->referral_source_id,
			unit_number => $request->patient_case->unit_number,
		);
		
		# load or create new patient_case:
		$patient_case = LIMS::DB::PatientCase->new(%args)->load_or_insert;
	}
	{ # change old patient_case_id to new patient_case_id for all requests:
		my $old_patient_case_id = $request->patient_case_id;
		
		my %args = (
			set   => { patient_case_id => $patient_case->id },
			where => [ patient_case_id => $old_patient_case_id ],
		);
		$i = LIMS::DB::Request::Manager->update_requests(%args);
	}
	{ # update patient_id in dependent tables (before cascade delete below):
		my $old_patient_case_id = $request->patient_case_id;

		my %args = (
			set   => { patient_id => $patient_case->id },
			where => [ patient_id => $old_patient_case_id ],
		);
		LIMS::DB::PatientTrial::Manager->update_patient_trials(%args);
		LIMS::DB::PatientEdit::Manager->update_patient_edits(%args);
	}
	{ # delete old patient_id from all tables:
		my $old_patient_id = $request->patient_case->patient_id;
		
		my %args = (
			id => $old_patient_id,
		);
		LIMS::DB::Patient->new(%args)->delete(cascade => 1); # replaces need for:		
		# LIMS::DB::PatientCase->new(id => $old_patient_case_id, db => $db)->delete;
		# LIMS::DB::Patient->new(id => $old_patient_id, db => $db)->delete;
	}
	
	return $i;
}
=cut

#-------------------------------------------------------------------------------
sub delete_patient {
    my $self = shift;
    my $patient_id = shift;

	# return true if the row was deleted or did not exist, false otherwise
	my $result =
		LIMS::DB::Patient->new(id => $patient_id)->delete(cascade => 1);

    return $result; # will return true if success
}

#------------------------------------------------------------------------
sub patient_matches_count { 
    my $self = shift;
    my $args = shift; # DEBUG $args;

    $args->{require_objects} = 'patient_case';

    my $i = LIMS::DB::Patient::Manager->get_patients_count(%$args);

    return $i;
}

#-------------------------------------------------------------------------------
sub patient_request_count {
	my $self       = shift;
	my $patient_id = shift;

	my %args = (
		query => [ 'patient_case.patient_id' => $patient_id ],
		require_objects => 'patient_case',
	);

	my $n = LIMS::DB::Request::Manager->get_requests_count(%args);

	return $n;

=begin # generated sql
SELECT
COUNT(DISTINCT t1.id)
FROM
  requests t1
	JOIN patient_case t2 ON (t1.patient_case_id = t2.id)
WHERE
  t2.patient_id = ?
=cut
}

#-------------------------------------------------------------------------------
sub _get_patient_cases_from {
	my ($self, $from_id) = @_;
	
	# get patient_id / unique patient_id's of 'from' patient_case(s):
	my @pid_from;
	{
		my $case_from = LIMS::DB::PatientCase::Manager
			->get_patient_cases( query => [ id => $from_id ] );
		my %pid_from = map { $_->patient_id => 1 } @$case_from; # ignore duplicates
		
		@pid_from = keys %pid_from;
	}
	
	# get list of all patient_cases associated with 'from' patient(s):
	my %args = (
		query => [ patient_id => \@pid_from ], # auto conversion to 'IN' if a list
		require_objects => 'patient',
	);
	
	my $patient_cases
		= LIMS::DB::PatientCase::Manager->get_patient_cases(%args);
	
	return $patient_cases;
}

#-------------------------------------------------------------------------------
sub _archive_and_delete_patients {
	my ($self, $patient_case, $case_to) = @_; # arrayref, string (integer)
	
	my $pid_to = $case_to->patient_id;
	
	foreach my $patient_case (@$patient_case) {
		# freeze patient data in archive:
		my $archive = $self->_create_patient_archive($patient_case->patient);		
		
		# only archive it once (in case same patient occurs in >1 patient case):
		unless ( $self->_archive_exists($archive) ) {
			# change patient_id from old to new:
			$archive->{patient_id} = $pid_to;
	
			# add error_code_id:
			my $err_code_id = $self->error_codes_map->{'record merged'};
			$archive->error_code_id($err_code_id);
			
			$archive->save;
		}
		
		{ # update dependent tables (patient_edits, patient_trial)
			my %args = (
				new_patient_id => $pid_to,
				old_patient_id => $patient_case->patient_id,
			);
		
			$self->_update_dependent_tables(\%args);
		}
		
		# delete patient_case & patient:
		# $patient_case->delete; # cascade => 1 & ON DELETE CASCADE in tbl def doesn't work
		$patient_case->patient->delete(cascade => 1); # does the right thing!!
	}
}

#-------------------------------------------------------------------------------
sub _update_dependent_tables {
	my $self = shift;
	my $args = shift;
	
	# update any entries in patient_edits table:
	LIMS::DB::PatientEdit::Manager->update_patient_edits(
		set   => { patient_id => $args->{new_patient_id} },
		where => [ patient_id => $args->{old_patient_id} ],
	);
		
	{ # update any entries in patient_trial table:
		my %args = ( query => [ patient_id => $args->{new_patient_id} ] );
		my $i = LIMS::DB::PatientTrial::Manager->get_patient_trials_count(%args);
		# unless new patient.id already exists in patient_trial table: 		
		unless ($i) { # old record will be deleted in cascade below
			my %args = ( query => [ patient_id => $args->{old_patient_id} ] );
			my $patient_trials
				= LIMS::DB::PatientTrial::Manager->get_patient_trials(%args);
			foreach my $patient_trial (@$patient_trials) {
				# update patient_id:
				$patient_trial->patient_id($args->{new_patient_id});
				$patient_trial->save(changes_only => 1);
			}
		}
	}
}		

#-------------------------------------------------------------------------------
sub _archive_exists {
	my ($self, $archive) = @_; # DB::PatientEdit object
	
	my %data = map {
		$_ => $archive->$_;
	} qw(last_name first_name middle_name dob nhs_number gender);
	
	my $count = LIMS::DB::PatientEdit::Manager
		->get_patient_edits_count( query => [ %data ] );
	
	return $count;
}

#-------------------------------------------------------------------------------
# takes data from form params and updates object with it - could move to superclass
sub _update_object_with_data {
    my $self = shift; # DEBUG $self;
    my $args = shift; # DEBUG $args;

    my $o    = $args->{object};
    my $data = $args->{data};

    my $changes = [];

    # get table col names:
    my @cols = $o->meta->column_names; # DEBUG \@cols;

    FIELD: foreach my $field ( @cols ) { # DEBUG [ 'PRE:', $field, $o->$field, $data->{$field} ];
        next FIELD if ( ! grep $field eq $_, keys %$data ); # only want form params (ie not id, time, etc)

        my $new_value = $data->{$field};

        # get col type - for new vs old comparison (ie '==' or 'eq'):
        my $col_type = $o->meta->column($field)->type; # DEBUG $type;

        { # localise for 'no warnings':
            no warnings 'uninitialized'; # possible empty fields
            next FIELD if
                $col_type eq 'integer' ? # determine col type for '==' or 'eq' comparator:
                    $o->$field == $new_value : # integer
                        lc $o->$field eq lc $new_value; # non-integer, lc both so case not considered
        }

        # collect details of change (field name, old value, new value):
        push @$changes, [ $field, $o->$field, $data->{$field} ]; # TODO - works but no use if date (get DT object!!)

        # set new value:
        $o->$field($new_value);
    }
}

#-------------------------------------------------------------------------------
sub _create_patient_archive {
	my ($self, $patient) = @_;	
	
	# take existing patient data (not pk or timestamps) into %data hash:
	my %archive = map { $_ => $patient->$_ }
		grep { $patient->meta->column($_)->type !~ m(serial|timestamp) }
			$patient->meta->column_names;

	# patient_id = patient->id:
	$archive{patient_id} = $patient->id;
	# user_id from UserProfile (from $args to new):
	$archive{user_id} = $self->user_profile->{id};	

	# create new patient object from %data:
	my $archive = LIMS::DB::PatientEdit->new(%archive);

	return $archive;
}

#-------------------------------------------------------------------------------
sub _build_error_codes_map {
	my $self = shift;
	
	my $error_codes = LIMS::DB::ErrorCode::Manager->get_error_codes;
	
	my %h = map { $_->description => $_->id	} @$error_codes;
	
	return \%h;
}

1;

__END__
=begin # old method
sub _new_patient {
    my $self = shift;
    my $data = shift; # DEBUG $data;

    my @patient_cols = LIMS::DB::Patient->meta->column_names; # DEBUG \@patient_cols;
    my @cases_cols   = LIMS::DB::PatientCase->meta->column_names; # DEBUG \@cases_cols;

    my %patient_data = map {
        $_ => $data->{$_};
    } grep $data->{$_}, @patient_cols; # DEBUG \%params;

    my %cases_data = map {
        $_ => $data->{$_};
    } grep $data->{$_}, @cases_cols; # DEBUG \%params;

    my $patient      = LIMS::DB::Patient->new(%patient_data);
    my $patient_case = LIMS::DB::PatientCase->new(%cases_data);

    # add patient object to patient_case:
    $patient_case->patient($patient);

    $patient_case->save;

    return $patient_case->id;
}
=cut

#-------------------------------------------------------------------------------
=begin # when edit_patient data submitted as patient case:
sub _update_patient {
    my $self = shift;
    my $data = shift; # DEBUG $data; # hashref of case_id & params

    my $case_id = $data->{case_id}
        || die 'no case_id passed to update_patient';

	$self->_do_name_cases($data);

    my $patient_case = LIMS::DB::PatientCase->new(id => $case_id)->load;
    $self->_update_object_with_data({ object => $patient_case, data => $data->{params} });

    my $patient_id = $patient_case->patient_id
        || die 'cannot retrieve patient_id in update_patient';

    my $patient = LIMS::DB::Patient->new(id => $patient_id)->load;
    $self->_update_object_with_data({ object => $patient, data => $data->{params} });

    # add patient object to patient_case object:
    $patient_case->patient($patient);

    # combined save:
    $patient_case->save;

    return $patient_case->db->error if $patient_case->db->error;
}
=cut


=begin # same as update_patient, but uses do_transaction:
sub _update_patient {
    my $self = shift;
    my $data = shift; # DEBUG $data;

    my $case_id = $data->{case_id};
    my $param   = $data->{params};

    my $db = LIMS::DB->new_or_cached;

    $db->do_transaction( sub {
        my $patient_case =
            LIMS::DB::PatientCase->new(
                db => $db,
                id => $case_id,
            )->load;

        my $patient_id = $patient_case->patient_id
            || die 'cannot retrieve patient_id in update_patient';

        # set patient_case.unit_number:
        $patient_case->unit_number($param->{unit_number});

        my $patient =
            LIMS::DB::Patient->new(
                db => $db,
                id => $patient_id,
            )->load;


        # get patient table cols:
        my @cols = $patient->meta->column_names; # DEBUG \@cols;

        # set patient table cols to form params:
        FIELD: foreach my $field ( @cols ) {
            my $value = $param->{$field} || next FIELD;
            $patient->$field($value);
        }

        # save patient:
        $patient->save;
        # save unit_number:
        $patient_case->save;

    });

    return 'update_patient() error - ' . $db->error if $db->error;
}
=cut

