#!/usr/bin/perl -Ilibs -I../www-multiLocation/libs

package API::LocationAdmin;

use strict;
use warnings;
use autodie;

use JSON;
use List::MoreUtils qw( uniq );
use Moose;
use Try::Tiny;
use Scalar::Util qw(blessed reftype);

use API::Controller ();
extends 'API::Controller';

use ECS::Warehouse;
use ECS::Warehouse::Location;
use ECS::Warehouse::LPN;
use ECS::WarehouseOS::PickSequence;

__PACKAGE__->meta->make_immutable;


###############################################################################

sub viewIndex {
    my $self = (shift)->new({'input' => shift || {}});
    $self->data->{'templates'} = [ "index.html" ];

    my $zoneName = "zoneName"; 
    $self->data->{'zoneName'} = $zoneName;


    # find all the valid sizes and types
    my $allSizings = ECS::Warehouse::Location::Sizing::search({
        'dbh'    => $self->dbhr,
        'search' => [],
    });
    my %sizes = ();
    my %types = ();
    my $parkingSpotLocationSize;
    for my $sizing (@{ $allSizings }) {
        $sizes{ $sizing->size } = 1;
        $types{ $sizing->type } = 1;
        if ($sizing->size eq 'PS') {
            $parkingSpotLocationSize = {
                ID => $sizing->ID,
                length => $sizing->length,
                width => $sizing->width,
                height => $sizing->height,
            };
        }
    }
    $self->data->{'sizes'} = [ keys %sizes ];
    $self->data->{'types'} = [ keys %types ];
    $self->data->{'parkingSpotLocationSize'} = $parkingSpotLocationSize;

    my $allFunctions = ECS::Warehouse::Location::Function::search({
        'dbh'    => $self->dbhr,
        'search' => [],
    });
    # de-bless the functions to allow serialization
    $self->data->{'functions'} = [ map { $_->pack } @{ $allFunctions } ];
	
	my $allWarehouses = ECS::Warehouse::search({
        'dbh'    => $self->dbhr,
        'search' => [],
    });
	
	my $allWarehousesZoneIDs = ECS::Warehouse::Location::WarehouseZone::search({
        'dbh'    => $self->dbhr,
        'search' => [],
    });
	
	#$allWarehousesZoneIDs = [ sort { $b->{zoneName} eq $a->{zoneName} } map { {warehouseZoneID => $_->warehouseZoneID, zoneName => $_->zoneName} } @{ $allWarehousesZoneIDs } ];
    $allWarehousesZoneIDs = [ sort { $b->zoneName eq $a->zoneName } @$allWarehousesZoneIDs ];
	
    # de-bless the functions to allow serialization
    $self->data->{'warehouses'} = [ map { $_->pack } @{ $allWarehouses } ];
    $self->data->{'warehouseZoneIDs'} = [ map $_->pack, @$allWarehousesZoneIDs ];

    return $self->result;
}

###############################################################################

sub printLabels {
    my $self = (shift)->new({'input' => shift || {}});

    $self->data->{'dataType'} = "json";

    my @locIDs = ();
    try {
        my $json = JSON->new;
        @locIDs = @{ $json->decode($self->input->{'locIDs'}) };
    }
    catch {
        $self->error("Invalid locIDs: $_");
        return $self->result;
    };

    my $locations = ECS::Warehouse::Location::search({
        'dbh'    => $self->dbhr,
        'search' => [{
            'locID' => \@locIDs,
        }],
    });
    for my $location (@{ $locations }) {
        push @{ $self->data->{'labels'} }, $location->labelData;
    }

    $self->data->{ 'data' }->{ 'printer' } = $self->input->{ 'printer' };
    $self->data->{'data'}->{'success'} = 1;
    return $self->result;
}

###############################################################################

sub getLocations {
    my $self = (shift)->new({'input' => shift || {}});
    #my $formSubmitted = $self->input->{'formSubmitted'} || 0;
    #my $formSubmitted = 1;

    $self->data->{'dataType'} = "json";

    my ($locations, @fields, $warehouseZones);
    try {
        my $json = JSON->new;
        my @search = @{ $json->decode($self->input->{'search'}) };
        @fields = @{ $json->decode($self->input->{'fields'}) };
		$search[1] ||= {};
		$search[1]{join} = ['zone'];
        $locations = ECS::Warehouse::Location::search({
            'dbh'    => $self->dbhr,
            'search' => \@search,
        });
		$warehouseZones = ECS::Warehouse::Location::WarehouseZone::search({
			'dbh'    => $self->dbhr,
			'search' => [],
		});
        1;
    }
    catch {
        $self->error("Failed to search: $_");
        0;
    }
    or return $self->result;

    for my $location (@{ $locations }) {
        my %locData = ();
        for my $field (@fields) {
            $locData{$field} = $location->$field;
        }
		if( defined $location->zone ){
			$locData{'warehouseZoneName'} = $location->zone->zoneName;
		} else {
			$locData{'warehouseZoneName'} = '';
		}
		$locData{'defaultEstimate'} = $location->defaultEstimate;
		
        push @{ $self->data->{'data'}->{'locations'} }, \%locData;
    }

    $self->data->{'data'}->{'success'} = 1;
    return $self->result;
}

###############################################################################

sub getChangeLogLocations {
    my $self = (shift)->new({'input' => shift || {}});

    $self->data->{'dataType'} = "json";

    my ($locations, @fields, $warehouseZones);
    try {
        my $json = JSON->new;
        my @search = @{ $json->decode($self->input->{'search'}) };
        @fields = @{ $json->decode($self->input->{'fields'}) };
		$search[1] ||= {};
		$search[1]{prefetch} = ['zone'];
        $locations = ECS::Warehouse::Location::search({
            'dbh'    => $self->dbh,
            'search' => \@search,
        });
        1;
    }
    catch {
        $self->error("Failed to search: $_");
        0;
    }
    or return $self->result;

    for my $location (@{ $locations }) {
        my %locData = ();
        for my $field (@fields) {
            $locData{$field} = $location->$field;
        }
		if( defined $location->zone ){
			$locData{warehouseZoneName} = $location->zone->zoneName;
		} else {
			$locData{'warehouseZoneName'} = '';
		}
		
        push @{ $self->data->{'data'}->{'locations'} }, \%locData;
    }

    $self->data->{'data'}->{'success'} = 1;
    return $self->result;
}

###############################################################################

sub getChangeLog {
    my $self = (shift)->new({'input' => shift || {}});

    $self->data->{'dataType'} = "json";
    my $logRecords;
	
    try {
        $logRecords = ECS::Warehouse::Location::getLocationChangeLog({
            'dbh'    => $self->dbhr,
        });
        1;
    }
    catch {
        $self->error("Failed to get change log: $_");
        0;
    }
    or return $self->result;

    $self->data->{'data'}->{'success'} = 1;
    $self->data->{'data'}->{'logRecords'} = $logRecords;
    return $self->result;
}

###############################################################################

sub saveCreatedLocations {
    my $self = (shift)->new({'input' => shift || {}});

    $self->data->{'dataType'} = "json";
    try {
        my $json = JSON->new;
        my @create = $json->decode($self->input->{'locations'});
        my $create = $create[ 0 ];

        # when creating active locations,
        # fetch data to determine if they are duplicates
        my %alreadyActive = ();
        if ( $create->{ 'active' } eq "Y" ) {
            my $locations = ECS::Warehouse::Location::search( {
                'dbh'    => $self->dbh,
                'search' => [
                    {
                        'active'   => "Y",
                        'isle'     => $create->{ 'isles'     },
                        'line'     => $create->{ 'lines'     },
                        'row'      => $create->{ 'rows'      },
                        'position' => $create->{ 'positions' },
                    },
                ],
            } );
            for my $location ( @{ $locations } ) {
                $alreadyActive{ $location->toString } = 1;
            }
        }

        my $createResult = ECS::Warehouse::Location::create({
            'dbh'           => $self->dbh,
            'create'        => \@create,
            'alreadyActive' => \%alreadyActive,
        });
        1;
    }
    catch {
        $self->error("Failed to create new locations: $_");
        0;
    }
    or return $self->result;

    $self->data->{'data'}->{'success'} = 1;
    return $self->result;
}

###############################################################################

sub saveLocations {
    my $self = (shift)->new({'input' => shift || {}});

    $self->data->{'dataType'} = "json";

    my $locData;
    try {
        my $json = JSON->new;
        $locData = $json->decode($self->input->{'locations'});
        1;
    }
    catch {
        $self->error("Failed to save locations: $_");
        0;
    }
    or return $self->result;

    my @locIDs = keys %{ $locData };
    my $locations = ECS::Warehouse::Location::search({
        'dbh'    => $self->dbh,
        'search' => [{
            'locID' => \@locIDs,
        }],
    });

    # check if any of these are already active in the DB
    my $sameLocations = ECS::Warehouse::Location::search( {
        'dbh'    => $self->dbh,
        'search' => [
            {
                'me.locID'            => \@locIDs,
                'sameLocation.active' => "Y",
            },
            {
                'prefetch' => "sameLocation",
            },
        ],
    } );
    my %alreadyActive = ();
    for my $location ( @{ $sameLocations } ) {
        $alreadyActive{ $location->toString } = 1;
    }

    # check if any of these have products
    my $productLocations = ECS::Warehouse::Location::search( {
        'dbh'    => $self->dbh,
        'search' => [
            {
                'me.locID' => \@locIDs,
                'products.productID' => { '!=' => undef },
            },
            {
                'prefetch' => "products",
                'collapse'  => 1,
            },
        ],
    } );
    my $productAltLocations = ECS::Warehouse::Location::search( {
        'dbh'    => $self->dbh,
        'search' => [
            {
                'me.locID'                => \@locIDs,
                'productAltLocations.qty' => { '>' => 0 },
            },
            {
                'prefetch' => "productAltLocations",
            },
        ],
    } );
    my %containsProducts = ();
    for my $location ( @{ $productLocations }, @{ $productAltLocations } ) {
        $containsProducts{ $location->toString } = 1;
    }

    my @warnings = ();
    for my $location (@{ $locations }) {
        my $changes = $locData->{ $location->locID };

        # apply the changes to the location
        for my $prop (keys %{ $changes }) {
            next if $prop eq "locID";
            my $value = $changes->{$prop};
            next if $value eq $location->$prop;

            if ( $prop eq "active" ) {
                if ( $value eq "Y" ) {
                    # prevent duplicate locations
                    if ( $alreadyActive{ $location->toString } ) {
                        my $message = sprintf(
                            "Cannot activate %s, would result in duplicate",
                            $location->toString,
                        );
                        push @warnings, $message;
                        next;
                    }
                    $alreadyActive{ $location->toString } = 1;
                }
                elsif ( $containsProducts{ $location->toString } ) {
                    # prevent inactive locations with products in them
                    my $message = sprintf(
                        "Cannot deactivate %s, assigned to a product",
                        $location->toString,
                    );
                    push @warnings, $message;
                    next;
                }
            }

            if ( $prop eq "locFunctionID"
                and $containsProducts{ $location->toString }
                ) {
                my $message = sprintf(
                    "Cannot change function of %s, assigned to a product",
                    $location->toString,
                );
                push @warnings, $message;
                next;
            }
			
			if( $prop eq 'warehouseZoneID' && $changes->{$prop} eq ''){
				$value = undef;
			}

			if( $prop eq 'estimate' && $changes->{$prop} eq ''){
				$value = undef;
			}

            $location->$prop($value);
        }
		$location->{employeeName} = $self->{input}->{employeeName};
        $location->save;
	}

    $self->data->{'data'}->{'warnings'} = \@warnings if @warnings;
    $self->data->{'data'}->{'success'} = 1;
    return $self->result;
}
###############################################################################
# used to get logs 
#
sub searchLogs {
    my $self = (shift)->new({'input' => shift || {}});
    $self->data->{'dataType'} = "json";
    my $logRecords;
	
    try {
        $logRecords = ECS::Warehouse::Location::searchChangeLog($self);
        1;
    }
    catch {
        $self->error("Failed to get change log: $_");
        0;
    }
    or return $self->result;

    $self->data->{'data'}->{'success'} = 1;
    $self->data->{'data'}->{'logRecords'} = $logRecords;

    return $self->result;
}

###############################################################################
# used to set a location active or deactive
#
sub setActiveState {
    my $self = (shift)->new({'input' => shift || {}});

    $self->data->{'dataType'} = "json";
    try {
        my $json = JSON->new;
        my $loc = $json->decode($self->input->{'locations'});
        
        my $locNames = $json->decode($self->input->{'locationNames'});
        my $employeeName = $self->input->{'employeeName'};
		my $state = $self->input->{'state'};

        # check for products in locations, if trying to deactivate
        if ($state eq 'N') {
            my $productLocations = ECS::Warehouse::Location::search( {
                'dbh'    => $self->dbh,
                'search' => [
                    {
                        'me.locID' => $loc->{ 'locations' },
                        'products.productID' => { '!=' => undef },
                    },
                    {
                        'prefetch' => "products",
                        'collapse' => "1",
                    },
                ],
            } );
            my $productAltLocations = ECS::Warehouse::Location::search( {
                'dbh'    => $self->dbh,
                'search' => [
                    {
                        'me.locID'                => $loc->{ 'locations' },
                        'productAltLocations.qty' => { '>' => 0 },
                    },
                    {
                        'prefetch' => "productAltLocations",
                        'collapse' => "1",
                    },
                ],
            } );
            my %containsProducts = ();
            for my $location ( @{ $productLocations }, @{ $productAltLocations } ) {
                $containsProducts{ $location->toString } = 1;
            }
            if ( %containsProducts ) {
                my $errMsg = "Can't deactivate locations that currently have products assigned: "
                        . join( ", ", uniq %containsProducts );
                $self->data->{ 'data' }->{ 'err' } = $errMsg;
                return 0;
            }
        }
        # duplicates in the DB
        my $sameLocations = ECS::Warehouse::Location::search( {
            'dbh'    => $self->dbh,
            'search' => [
                {
                    'me.locID'            => $loc->{ 'locations' },
                    'sameLocation.active' => "Y",
                },
                {
                    'prefetch' => "sameLocation",
                },
            ],
        } );
        my @duplicateLocations = map { $_->toString } @{ $sameLocations };

        # duplicates in the submission
        my %seen = ();
        for my $locName ( @{ $locNames->{ 'locationNames' } } ) {
            push @duplicateLocations, $locName if $seen{ $locName };
            $seen{ $locName } ++;
        }

        if ( @duplicateLocations ) {
            my $errMsg = "Unable to activate location range, "
                       . "would result in duplicate active locations: "
                       . join( ", ", uniq @duplicateLocations );
            $self->data->{ 'data' }->{ 'err' } = $errMsg;
            return 0;
        }

        my $activateResult = ECS::Warehouse::Location::setActiveField({
            'dbh'    => $self->dbh,
            'locations' => $loc->{locations},
			'locationNames' => $locNames->{locationNames},
			'employeeName' => $employeeName,
			'state'        => $state,
        });
        1;
    }
    catch {
        $self->error("Failed to set Active State : $_");
        0;
    }
    or return $self->result;

    $self->data->{'data'}->{'success'} = 1;
    return $self->result;
}

###############################################################################

sub setPickSequence {
    my $self = (shift)->new({ input => shift || {}});

    # double check if any sequence upload is already in progress
    # otherwise make entry for new log
    my $insertSuccess = ECS::WarehouseOS::PickSequence::insertSequenceLog({
        'dbh' => $self->dbh,
        'dbhr' => $self->dbhr,
        'input' => $self->input,
    });
    if ($insertSuccess == 0) {
        $self->data->{'data'}->{'success'} = 1;
        $self->data->{'dataType'} = "json";
        $self->data->{'data'}->{'insertLogMessage'} = "The last sequence upload is still in progress. Please try again later.";

        return $self->result;
    }
    
    $self->data->{'templates'} = [ "index.html" ];

    my @pickSequence;
    @pickSequence = @{ $self->input->{pickSequence} || [] };
    unless (@pickSequence) {
        $self->error("Failed to set Pick Sequence");
        return $self->result;
    }

    my $allLocations = ECS::Warehouse::Location::search({
        'dbh'    => $self->dbhr,
        'search' => [],
    });

    my $locations = {
        map {
            join('', ($_->isle, $_->line, $_->row, $_->position)) => $_->locID
        } @{ $allLocations }
    };

    my @locPickSequence = map { $locations->{$_} } grep { exists $locations->{$_} } @pickSequence;
    try {
        ECS::Warehouse::Location::setPickSequence({
            'dbh'    => $self->dbh,
            'pickSequence' => \@locPickSequence
        });
        $self->data->{'data'}->{'success'} = 1;
        $self->data->{ uploadMessage } = 'Pick Sequence Set in DB';
    }
    catch {
        $self->error("Can't set Pick Sequence in DB: $_");
    };

    return $self->result;
}

###############################################################################

sub getPickSequence {
    my $self = (shift)->new({ input => shift || {}});
    my $pickSequence = ECS::Warehouse::Location::getPickSequence({
        dbh     => $self->dbhr,
    });
    $self->data->{'data'}->{'success'} = 1;
    $self->data->{ csv } = $pickSequence;
    $self->data->{ dataType } = 'text/csv';
    return $self->result;
    
}
###############################################################################

sub createLPN {
	my $self = (shift)->new({'input' => shift || {}});

	$self->data->{'dataType'} = "json";
	my $numberOfLPNToCreate = $self->input->{'numberOfLPNToCreate'};

    my $LPNData = ECS::Warehouse::LPN::createLPN({
        'dbh'    => $self->dbh,
        'numberOfLPNToCreate' => $numberOfLPNToCreate,
    });

	if (! scalar @$LPNData) {
		$self->error("Failed to create LPNs");
		return $self->result;
	}
	
	$self->data->{'data'}->{'lpn'} = $LPNData;

    $self->data->{'data'}->{'success'} = 1;
    return $self->result;
}

sub updateLPNStatus {
	my $self = (shift)->new({'input' => shift || {}});

	$self->data->{'dataType'} = "json";
	my $lpnIDs;
    try {
        my $json = JSON->new;
        $lpnIDs = $json->decode($self->input->{'LPNIDs'});
        1;
    }
    catch {
		warn $_;
        $self->error("Failed to deactivate LPNs: $_");
        0;
    }
    or return $self->result;
	
	my $LPNData = ECS::Warehouse::LPN::updateLPNStatus({
        'dbh'    => $self->dbh,
        'lpnIDs' => $lpnIDs,
		'updateTo' => $self->input->{'updateTo'},
    });

    $self->data->{'data'}->{'success'} = 1;
    return $self->result;
}

sub searchLPN {
	my $self = (shift)->new({'input' => shift || {}});

	$self->data->{'dataType'} = "json";
	my $lpnIDs;
    try {
        my $json = JSON->new;
        $lpnIDs = $json->decode($self->input->{'lpnIDs'});
        1;
    }
    catch {
		warn $_;
        $self->error("Failed to deactivate LPNs: $_");
        0;
    }
    or return $self->result;
	
	my $LPNData = ECS::Warehouse::LPN::searchLPN({
        'dbh'    => $self->dbhr,
        'lpnIDs' => $lpnIDs,
		'status' => $self->input->{'status'},
		'numberOfLPNToSearch' => $self->input->{'numberOfLPNToSearch'},
    });

	if (! scalar @$LPNData) {
		$self->error("Failed to create LPNs");
		return $self->result;
	}

	$self->data->{'data'}->{'lpn'} = $LPNData;
    $self->data->{'data'}->{'success'} = 1;
    return $self->result;
}

sub printLPNLabels {
	my $self = (shift)->new({'input' => shift || {}});

	$self->data->{'dataType'} = "json";
	my $lpnIDs;
    try {
        my $json = JSON->new;
        $lpnIDs = $json->decode($self->input->{'lpnIDs'});
        1;
    }
    catch {
		warn $_;
        $self->error("Failed to print LPN labels: $_");
        0;
    }
    or return $self->result;
	
	my $labelData = ECS::Warehouse::LPN::getLabelData({
        'dbh'    => $self->dbh,
        'lpnIDs' => $lpnIDs,
    });

    if (! scalar @$labelData) {
		$self->error("Failed to get LPN labels");
		return $self->result;
	}

	$self->data->{'labels'} = $labelData;
    $self->data->{'data'}->{'printer'} = $self->input->{'printer'};
    $self->data->{'data'}->{'success'} = 1;
    return $self->result;
}

# assign LPN to order
sub assignLPN {
	my $self = (shift)->new({input => shift || {} });
	return unless (@@=@{ $self->input }{ qw/lpnID orderID/}) == 2;
	return ECS::Warehouse::LPN::assignLPNtoOrder({
		%{ $self->input },
		dbh		=> $self->dbh,
	});
}

###############################################################################

sub editPKSLocationSize {
    my $self = (shift)->new({input => shift || {} });

    $self->data->{'dataType'} = "json";
    my $dimensions;
    try {
        my $json = JSON->new;
        $dimensions = $json->decode($self->input->{'dimensions'});
        1;
    }
    catch {
		warn $_;
        $self->error("Failed to edit parking spot location size: $_");
        0;
    }
    or return $self->result;

    try {
        my $sth = $self->dbh->prepare("UPDATE locSizing SET length = ?, width = ?, height = ? WHERE ID = ?");
        $sth->execute($dimensions->{length}, $dimensions->{width}, $dimensions->{height}, $self->input->{'ID'});
        1;
    } catch {
        warn $_;
        $self->error("Failed to edit parking spot location size: $_");
        0;
    }
    or return $self->result;
    
    $self->data->{'data'}->{'success'} = 1;
    return $self->result;
}

sub updateZone {
    my $self = (shift)->new({'input' => shift || {}});
    $self->data->{'templates'} = [ "save.html" ];
    $self->data->{partial} = 1;
    
    my $zoneID           = $self->input->{'zoneID'};
    my $zoneName         = $self->input->{'zoneName'};
    my $daysOfSupply     = $self->input->{'daysOfSupply'};
    my $last90DayMinimum = $self->input->{'last90DayMinimum'};
    my $last90DayMaximum = $self->input->{'last90DayMaximum'};
    my $acceptNew = defined $self->input->{'acceptNew'} || 0;
    my $acceptJIT = defined $self->input->{'acceptJIT'} || 0;

    try {
            my $warehouseZone = ECS::Warehouse::Location::WarehouseZone->new({
            'dbh' => $self->dbh,
            'warehouseZoneID' => $zoneID,
        });
        
        if ($warehouseZone) {
            $warehouseZone->zoneName($zoneName);
            $warehouseZone->daysOfSupply($daysOfSupply);
            $warehouseZone->last90DayMinimum($last90DayMinimum);
            $warehouseZone->last90DayMaximum($last90DayMaximum);
            $warehouseZone->acceptNew($acceptNew);
            $warehouseZone->acceptJIT($acceptJIT);

            $warehouseZone->save;
            
            $self->data->{'success_flag'} = 1;

        } else {
            $self->error("Warehouse Zone not found with ID: $zoneID");
            die "Warehouse Zone not found with ID: $zoneID";
        }
        $self->data->{warehouseZoneID} = $zoneID;
        $self->data->{zoneName} = $zoneName;
        $self->data->{'data'}->{'success'} = 1;
        return $self->result;
    }
    catch {
        $self->data->{'data'}->{'success'} = 0;
        my $error_message = "Failed to update warehouse zone: $_";
        $self->error($error_message);
        $self->data->{'success_flag'} = 0;
    }
    or return $self->result;
}



1;
