<?php

namespace App\Modules\Company\Whiteboard\AssignWorker;

use App\Models\AssignedAvailability;
use App\Models\AssignedWorkers;
use App\Models\Availabilities;

class AssignWorkerWithoutConflict
{
    public function assign($companyId, $workerId, $userInputStartDate, $userInputEndDate, $workerType, $projectId)
    {
        $availabilitiesData = $this->fetchAvailabilities($companyId, $workerId, $userInputStartDate, $userInputEndDate, $workerType);
        if (count($availabilitiesData) == 1) {
            $availabilityId = $availabilitiesData[0]->id;
            $maxAvailabilityDate = $availabilitiesData[0]->max_availability_date;
            $minAvailabilityDate = $availabilitiesData[0]->min_availability_date;
            $workerRate = $availabilitiesData[0]->rate;
            $deleteAvailabilitiesTransaction = true;
            $updateAvailabilitiesTransaction = true;
            $createAvailabilitiesTransaction = true;
            if ($userInputStartDate == $minAvailabilityDate) {
                if ($userInputEndDate >= $maxAvailabilityDate) {
                    $deleteAvailabilities = $this->deleteAvailabilities($availabilityId);
                    if (!$deleteAvailabilities) {
                        $deleteAvailabilitiesTransaction = false;
                    }
                } else {
                    $isLastData = $this->checkAvailabilitiesIfLastData($minAvailabilityDate, $maxAvailabilityDate);
                    $availabilityData = array(
                        'min_availability_date' => date('Y-m-d', strtotime($userInputEndDate . '+1 day')),
                        'max_availability_date' => $isLastData ? date('Y-m-d', strtotime($userInputEndDate . '+10 years +1 day')) : $maxAvailabilityDate,
                    );
                    $updateAvailabilities = $this->updateAvailabilities($availabilityId, $availabilityData);
                    if (!$updateAvailabilities) {
                        $updateAvailabilitiesTransaction = false;
                    }
                }
            } else {
                $availabilityData = array(
                    'max_availability_date' => date('Y-m-d', strtotime($userInputStartDate . '-1 day')),
                );
                $updateAvailabilities = $this->updateAvailabilities($availabilityId, $availabilityData);
                if (!$updateAvailabilities) {
                    $updateAvailabilitiesTransaction = false;
                }
                if ($userInputEndDate < $maxAvailabilityDate) {
                    $isLastData = $this->checkAvailabilitiesIfLastData($minAvailabilityDate, $maxAvailabilityDate);
                    $availabilityData = array(
                        'worker_id' => $workerId,
                        'company_id' => $companyId,
                        'worker_type' => $workerType,
                        'rate' => $workerRate,
                        'min_availability_date' => date('Y-m-d', strtotime($userInputEndDate . '+1 day')),
                        'max_availability_date' => $isLastData ? date('Y-m-d', strtotime($userInputEndDate . '+10 years +1 day')) : $maxAvailabilityDate,
                    );
                    $createAvailabilities = $this->createAvailabilities($availabilityData);
                    if (!$createAvailabilities) {
                        $createAvailabilitiesTransaction = false;
                    }
                }
            }

            $createAssignedWorkersTransaction = $this->createAssignedWorkers($projectId, $workerId);
            $assignedWorkerId = $createAssignedWorkersTransaction->id;
            $createAssignedAvailabilityTransaction = $this->createAssignedAvailability($availabilityId, $assignedWorkerId, $workerType, $workerRate, $userInputStartDate, $userInputEndDate);
            $combineDurationsTransaction = $this->combineDurations($workerId, $projectId, $workerType, $userInputStartDate, $userInputEndDate);

            if ($deleteAvailabilitiesTransaction &&
                $updateAvailabilitiesTransaction &&
                $createAvailabilitiesTransaction &&
                $createAssignedWorkersTransaction &&
                $createAssignedAvailabilityTransaction &&
                $combineDurationsTransaction) {
                return $createAssignedAvailabilityTransaction;
            } else {
                return false;
            }
        }
    }

    public function fetchAvailabilities($companyId, $workerId, $userInputStartDate, $userInputEndDate, $workerType)
    {
        return Availabilities::select()
            ->where('availabilities.company_id', $companyId)
            ->where('availabilities.worker_id', $workerId)
            ->where('availabilities.worker_type', $workerType)
            ->where(function ($query) use ($userInputStartDate, $userInputEndDate) {
                $query->where('availabilities.min_availability_date', $userInputStartDate)
                    ->orWhere('availabilities.max_availability_date', $userInputEndDate)
                    ->orWhereBetween('availabilities.min_availability_date', [$userInputStartDate, $userInputEndDate])
                    ->orWhereBetween('availabilities.max_availability_date', [$userInputStartDate, $userInputEndDate])
                    ->orWhereRaw('? BETWEEN availabilities.min_availability_date and availabilities.max_availability_date', [$userInputStartDate])
                    ->orWhereRaw('? BETWEEN availabilities.min_availability_date and availabilities.max_availability_date', [$userInputEndDate]);
            })
            ->orderBy('availabilities.min_availability_date', 'asc')
            ->limit(1)
            ->get();
    }

    public function deleteAvailabilities($availabilityId)
    {
        return Availabilities::where('availabilities.id', $availabilityId)->delete();
    }

    public function updateAvailabilities($availabilityId, $availabilityData)
    {
        return Availabilities::where('availabilities.id', $availabilityId)->update($availabilityData);
    }

    public function createAvailabilities($availabilityData)
    {
        return Availabilities::create($availabilityData);
    }

    public function checkAvailabilitiesIfLastData($maxAvailabilityDate, $minAvailabilityDate)
    {
        $maxAvailabilityDate = strtotime($maxAvailabilityDate);
        $minAvailabilityDate = strtotime($minAvailabilityDate);
        $diff = abs($maxAvailabilityDate - $minAvailabilityDate);
        $years = floor($diff / (365 * 60 * 60 * 24));
        return $years == 10 ? true : false;
    }

    public function createAssignedWorkers($projectId, $workerId)
    {
        $assignedWorkersData = array(
            'project_id' => $projectId,
            'worker_id' => $workerId,
        );
        return AssignedWorkers::create($assignedWorkersData);
    }

    public function createAssignedAvailability($availabilityId, $assignedWorkerId, $workerType, $workerRate, $userInputStartDate, $userInputEndDate)
    {
        $assignedAvailabilityData = array(
            'availability_id' => $availabilityId,
            'assigned_worker_id' => $assignedWorkerId,
            'worker_type' => $workerType,
            'rate' => $workerRate,
            'start_date' => $userInputStartDate,
            'end_date' => $userInputEndDate,
            'is_draft' => 1,
        );
        return AssignedAvailability::create($assignedAvailabilityData);
    }

    public function combineDurations($workerId, $projectId, $workerType, $userInputStartDate, $userInputEndDate)
    {
        $userInputStartDateMinus1DayDate = date('Y-m-d', strtotime($userInputStartDate . '-1 day'));
        $userInputEndDatePlus1DayDate = date('Y-m-d', strtotime($userInputEndDate . '+1 day'));
        $assignedAvailabilityDataToCombine = $this->fetchAssignedAvailabilityDataToCombine($workerId, $projectId, $workerType, $userInputStartDateMinus1DayDate, $userInputEndDatePlus1DayDate);
		if (count($assignedAvailabilityDataToCombine) > 1) {
            $assignedAvailabilityIdsToDelete = [];
            $assignedWorkersIdsToDelete = [];
			$assignedAvailabilityFirstData = $assignedAvailabilityDataToCombine[0];
			$assignedAvailabilityFirstDataId = $assignedAvailabilityFirstData['id'];
			$assignedAvailabilityLastData = $assignedAvailabilityDataToCombine[count($assignedAvailabilityDataToCombine) - 1];
            $assignedAvailabilityLastDataEndDate = $assignedAvailabilityLastData['end_date'];
            foreach ($assignedAvailabilityDataToCombine as $assignedAvailability) {
                if ($assignedAvailability->id != $assignedAvailabilityFirstDataId) {
                    array_push($assignedAvailabilityIdsToDelete, $assignedAvailability->id);
                    array_push($assignedWorkersIdsToDelete, $assignedAvailability->assigned_worker_id);
                }
            }
            $updateAssignedAvailabilityEndDateTransaction = $this->updateAssignedAvailabilityEndDate($assignedAvailabilityFirstDataId, $assignedAvailabilityLastDataEndDate);
            $deleteAssignedWorkersTransaction = $this->deleteAssignedWorkers($assignedWorkersIdsToDelete);
            $deleteAssignedAvailabilityTransaction = $this->deleteAssignedAvailability($assignedAvailabilityIdsToDelete);
            if ($updateAssignedAvailabilityEndDateTransaction && 
                $deleteAssignedWorkersTransaction &&
                $deleteAssignedAvailabilityTransaction) {
                return $assignedAvailabilityFirstData;
            }
            return false;
		}
		return true;
    }

    public function fetchAssignedAvailabilityDataToCombine($workerId, $projectId, $workerType, $userInputStartDateMinus1DayDate, $userInputEndDatePlus1DayDate)
    {
        return AssignedAvailability::select(
            'assigned_availability.id',
            'assigned_availability.assigned_worker_id',
            'assigned_availability.start_date',
            'assigned_availability.end_date')
            ->join('assigned_workers', 'assigned_workers.id', '=', 'assigned_availability.assigned_worker_id')
            ->where('assigned_workers.worker_id', $workerId)
            ->where('assigned_workers.project_id', $projectId)
            ->where('assigned_availability.worker_type', $workerType)
            ->where('assigned_availability.is_draft', 1)
            ->where(function ($query) use ($userInputStartDateMinus1DayDate, $userInputEndDatePlus1DayDate) {
                $query->where('assigned_availability.start_date', $userInputEndDatePlus1DayDate)
					->orWhere('assigned_availability.end_date', $userInputStartDateMinus1DayDate)
                    ->orWhereBetween('assigned_availability.start_date', [$userInputStartDateMinus1DayDate, $userInputEndDatePlus1DayDate])
                    ->orWhereBetween('assigned_availability.end_date', [$userInputStartDateMinus1DayDate, $userInputEndDatePlus1DayDate])
                    ->orWhereRaw('? BETWEEN assigned_availability.start_date and assigned_availability.end_date', [$userInputStartDateMinus1DayDate])
                    ->orWhereRaw('? BETWEEN assigned_availability.start_date and assigned_availability.end_date', [$userInputEndDatePlus1DayDate]);
            })
            ->orderBy('assigned_availability.start_date', 'asc')
            ->orderBy('assigned_availability.end_date', 'asc')
            ->get();
    }

    public function updateAssignedAvailabilityEndDate($assignedAvailabilityFirstDataId, $assignedAvailabilityLastDataEndDate)
    {
        $assignedAvailabilityData = array(
            'end_date' => $assignedAvailabilityLastDataEndDate
        );
        return AssignedAvailability::where('assigned_availability.id', $assignedAvailabilityFirstDataId)->update($assignedAvailabilityData);
    }

    public function deleteAssignedWorkers($assignedWorkersIdsToDelete)
    {
        return AssignedWorkers::whereIn('assigned_workers.id', $assignedWorkersIdsToDelete)->delete();
    }

    public function deleteAssignedAvailability($assignedAvailabilityIds)
    {
        return AssignedAvailability::whereIn('assigned_availability.id', $assignedAvailabilityIds)->delete();
    }
}
