package Role::Patient;

use Moose::Role;
use Data::Dumper;

# get patient id :
sub get_patient_id {
    my $self = shift;
    my $vals = shift;

	my $dbh = $self->db->{dbix4};
	my $log_file = $self->log_file;
	
	my @h4 = qw(last_name first_name middle_name dob);
	my @h3 = qw(lname fname mname dob);
	
	# if $vals has an nhs_number:
	if ( my $nhs_number = $vals->{nhsno} ) {			
		# if nhs_number in list of resolved duplicates:
		if ( my $new_data = $self->resolved_nhsno_duplicates_map->{$nhs_number} ) {
			no warnings 'uninitialized'; # for all print statements:
			# return patient_id if already been here:
			if (my $patient_id = $self->nhs_number_map->{$nhs_number}) {
				my $sql = q!select last_name, first_name, middle_name, dob, gender, 
					nhs_number from patients where id = ?!;
				my $p = $dbh->query($sql, $patient_id)->hash;

				if ( (join '', map uc($_), @{$new_data}{@h3}) ne (join '', map uc($_), @{$p}{@h4}) ) {
					print $log_file 'have existing resolved nhsno mismatch for ' . $nhs_number . "\n";
					print $log_file join ',', map $p->{$_}, @h4; print $log_file "\n";
					print $log_file join ',', map $new_data->{$_}, @h3; print $log_file "\n";
				}
				return $patient_id; 
			}
			elsif ( (join '', map uc($_), @{$new_data}{@h3}) ne (join '', map uc($_), @{$vals}{@h3}) ) {
				print $log_file 'have new resolved nhsno mismatch for ' . $nhs_number . "\n";
				print $log_file join ',', map $vals->{$_}, @h3; print $log_file "\n";
				print $log_file join ',', map $new_data->{$_}, @h3; print $log_file "\n";
			}
			
			# set new params:
			$vals->{nhsno} = $nhs_number; # not stored as key in map_hashes
			while ( my ($param, $val) = each %$new_data ) {
				$vals->{$param} = $val;
			} # warn Dumper $vals;	
		}
		
		# first try to update any records with last_name, dob & unit_no match, but
		# WITHOUT nhsno (affects <100 of 45K recs):
		if ( $vals->{patno} && $vals->{dob} ) {
			my $sql = q!select p.id from patients p join patient_case pc on p.id =
				pc.patient_id where unit_number = ? and last_name = ? and dob = ?
				and nhs_number is null!;
			my $patient_id
				= $dbh->query( $sql, map $vals->{$_}, qw(patno lname dob) )->list;

			# update record (must be only 1) with nhs_number:
			if ($patient_id) {
				# check nhsno not already used - in which case need to flag for merge:
				if ($self->nhs_number_map->{$nhs_number}) {
					print $log_file "merge required on $vals->{lname} $vals->{fname}\n";
				}
				else { 
					$dbh->update('patients', {nhs_number => $nhs_number}, {id => $patient_id});
					print $log_file "updating $patient_id with $nhs_number for $vals->{dbid}\n";
					# cache & return it:
					$self->nhs_number_map->{$nhs_number} = $patient_id;					
				}
				return $patient_id;
			}
		}

		# update middle name if exists:
		if ( my $mname = $vals->{mname} ) { # already checked mname > 1 char
			my $sql = q!select p.id from patients p where nhs_number = ? and last_name = ?
				and first_name = ? and middle_name is null!;
			my $patient_id
				= $dbh->query( $sql, map $vals->{$_}, qw(nhsno lname fname) )->list;
			if ($patient_id) {
				print $log_file "updating $patient_id with $mname for $vals->{dbid}\n";
				$dbh->update('patients', {middle_name => $mname}, {id => $patient_id});
			}
		}
		
		# if not already seen this nhs_no, create new:
		if (! $self->nhs_number_map->{$nhs_number}) {
			my $patient_id = $self->_create_new_record($vals);		
			# now cache it:
			$self->nhs_number_map->{$nhs_number} = $patient_id;
		}
		# return patient_id:
		return $self->nhs_number_map->{$nhs_number};		
	}
	# else need to find best match or create new record:
	else {
		my $patient_id = $self->find_or_create_new_patient($vals);
		return $patient_id;
	}
}

# don't have an nhs_no, so find best match or create new patient & return LAST_INSERT_ID:
sub find_or_create_new_patient {
    my $self = shift;
    my $vals = shift;

    my $dbh = $self->db->{dbix4};

	my $log_file = $self->log_file;
	
	my $patient_id = 0;
	 
 	# ok, no nhs_number, see if we can find exact match on ALL pid params:	
	if ($vals->{dob} && $vals->{patno}) { # already know we have fname & lname
		{ # 1st see if we have a last_name, first_name & unit_no match with null dob:
			my $sql = q!select p.id from patients p join patient_case pc on p.id = pc.patient_id
				where unit_number = ? and last_name = ? and first_name = ? and dob is null!;
			$patient_id = $dbh->query( $sql, map $vals->{$_}, qw(patno lname fname) )->list;
			if ($patient_id) { # update dob:
				print $log_file "got $patient_id from matching details with null dob for $vals->{dbid}\n";
				$dbh->update('patients', { dob => $vals->{dob} }, {id => $patient_id});
				return $patient_id;
			}
		}

		my $middle_name =
			$vals->{mname} ?
				sprintf q!= %s!, $self->_quote_term($vals->{mname})
					: 'IS NULL';

		my $sql = sprintf q!
			SELECT p.id
			FROM patients p
				join patient_case pc on p.id = pc.patient_id
			WHERE
				last_name   = ? and
				first_name  = ? and
				middle_name  %s and
				dob         = ? and
				unit_number = ? and				
				nhs_number is null!, $middle_name; # warn $pid_sql;

		$patient_id
			= $dbh->query( $sql, map $vals->{$_}, qw(lname fname dob patno) )->list;
		if ($patient_id) {
			# print $log_file "got $patient_id from full matching details for $vals->{dbid}\n";
			return $patient_id;
		}			
	
		# next, try to get patient_id from existing combination of last_name,
		# dob & unit_no only (ie skip first_name):
		{
			my $sql = q!
				select p.id from patients p join patient_case pc on p.id = pc.patient_id
				where unit_number = ? and last_name = ? and dob = ?!;
		
			$patient_id
				= $dbh->query( $sql, map $vals->{$_}, qw(patno lname dob) )->list;
		   
			if ($patient_id) {
				print $log_file "got $patient_id from partial matching details for $vals->{dbid}\n";
				return $patient_id;
			}			
		}
	}

	# some known exceptions before we give up trying to find a match:
	if ($vals->{lname} eq 'CYCLACEL') { # cyclacels
		$patient_id = $dbh->query( q!select p.id from patients p where last_name = 
			'cyclacel' and first_name = ? and dob = ?!, $vals->{fname}, $vals->{dob})
		->list;
		return $patient_id if $patient_id;
	}
	elsif (grep $vals->{fname} eq $_, qw/LGI PIN/) { # HIV's
		$patient_id = $dbh->query( q!select p.id from patients p where last_name = ?
			and first_name = ?!, $vals->{lname}, $vals->{fname})->list;
		return $patient_id if $patient_id;		
	}
	
    # exhausted all attempts to match patient, so create new record & retrieve id:
	$patient_id = $self->_create_new_record($vals);
	# print $log_file "creating new record for $vals->{dbid}\n";
    return $patient_id;
}

sub find_or_create_new_patient_case {
    my $self = shift;
    my $data = shift;

    my $dbh = $self->db->{dbix4};

    my $case_id;

    {
    	# update any unknown hospital if patient_id & unit_number match:
		my $sql = q!SELECT pc.`id` FROM `patient_case` pc join referral_sources rs on
			pc.referral_source_id = rs.id WHERE pc.`patient_id` = ? AND
			pc.`unit_number` = ? AND rs.`organisation_code` = 'X99999'!; # unknown source

        $case_id = $dbh->query( $sql, $data->{patient_id}, $data->{unit_number} )->list;

		if ($case_id) {
			$dbh->update( 'patient_case', { referral_source_id => $data->{referral_source_id} },
				{id => $case_id} );
			# print $log_file "updating $case_id with ".$data->{referral_source_id}."\n";
		}

        return $case_id if $case_id;
	}

    {
        # update any unknown unit_numbers if patient_id & location_id match:
		my $sql = q!SELECT `id` FROM `patient_case` WHERE `patient_id` = ? AND
			`referral_source_id` = ? AND `unit_number` = 'UNKNOWN'!; # unknown source

        $case_id = $dbh->query($sql, $data->{patient_id}, $data->{referral_source_id})->list;

		if ($case_id) {
			$dbh->update( 'patient_case', { unit_number => $data->{unit_number} },
				{id => $case_id} );
			# print $log_file "updating $case_id with ".$data->{unit_number}."\n";
		}

        return $case_id if $case_id;
	}

    # failed to find matching case_id so create one:
    $dbh->insert('patient_case', $data);

    $case_id = $dbh->last_insert_id(undef, undef, 'patient_case', 'id');

    return $case_id;
}

sub get_case_id {
    my $self = shift;
    my $data = shift;

    my $sql = q!SELECT `id` FROM `patient_case` WHERE `patient_id` = ? AND
        `referral_source_id` = ? AND `unit_number` = ?!;

    my $case_id = $self->db->{dbix4}->query(
            $sql, @{$data}{ qw(patient_id referral_source_id unit_number) }
        )->list;

    return $case_id;
}

sub _create_new_record {
	my $self = shift;
	my $vals = shift;
	
	my $dbh = $self->db->{dbix4};
	
	# data for creating new patient (required):
    my %data = (
        last_name  => lc $vals->{lname},
        first_name => lc $vals->{fname},
        created_at => $vals->{pid_time},
		updated_at => $vals->{pid_time}, # or defaults to now()
    );

	# add data if exists:
	$data{middle_name} = lc $vals->{mname} if $vals->{mname};
	$data{dob} 	       = $vals->{dob}      if $vals->{dob};
    $data{gender}      = $vals->{sex}      if $vals->{sex};
    $data{nhs_number}  = $vals->{nhsno}    if $vals->{nhsno};
		
	$dbh->insert('patients', \%data); # warn Dumper \%data;
	my $patient_id = $dbh->last_insert_id(undef, undef, 'patients', 'id');
	return $patient_id;
}

sub _quote_term {
    my $self = shift;
    my $term = shift;

    return $self->db->{dbh3}->quote($term);
}

1;
