<?php

namespace App\Modules\Company\Whiteboard;

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

use App\Events\Company\DeleteAssignedAvailabilityEvent;
use App\Events\Company\UpdateAssignedAvailabilityEndDateEvent;

use App\Traits\CreateUserActionTraits;
use App\Traits\UserTraits;
use App\Traits\ValidatorTraits;
use App\Traits\CreateMobileNotificationTraits;
use Illuminate\Support\Facades\DB;

class RemoveWorkerFromProject
{
    use UserTraits, CreateUserActionTraits, ValidatorTraits, CreateMobileNotificationTraits;

    public function removeWorker($assignedWorkerId)
    {
        /*========================================================================================================
        Start Transaction
        ========================================================================================================*/
        DB::beginTransaction();

        // variables to use
        $companyId = $this->getCurrentUser()->company_id;
        $assignedWorkersData = $this->fetchAssignedWorkersData($assignedWorkerId);
        $workerType = $assignedWorkersData->worker_type;
        $startDate = $assignedWorkersData->start_date;
        $endDate = $assignedWorkersData->end_date;
        $isDraft = $assignedWorkersData->is_draft;
        $workerId = $assignedWorkersData->worker_id;
        $dateToday = date('Y-m-d');
        $dateYesterday = date('Y-m-d', strtotime('-1 day'));
        $CRUDAvailabilitiesTransaction = true;
        $CRUDAssignedAvailabilityTransaction = true;
        $CRUDAssignedWorkersTransaction = true;

        if ($endDate < $dateToday && $isDraft == 0) {
            return response()->json(['error' => 'Unable to remove employee for the past dates.'])->setStatusCode(201);
        }

        // fetch latest worker rate from availabilities table
        $workerRate = $this->fetchWorkerRateFromAvailabilitiesTable($companyId, $workerId, $workerType)->rate;

        if ($startDate < $dateToday && $isDraft == 0) {
            // update assigned availability and update project workers timesheet
            $assignedAvailabilityData = array(
                'end_date' => $dateYesterday,
            );
            $CRUDAssignedAvailabilityTransaction = $this->updateAssignedAvailability($assignedWorkerId, $assignedAvailabilityData, $endDate);

            // create availabilities
            $availabilitiesData = array(
                'worker_id' => $workerId,
                'company_id' => $companyId,
                'worker_type' => $workerType,
                'rate' => $workerRate,
                'min_availability_date' => $dateToday,
                'max_availability_date' => $endDate,
            );
            $createAvailabilities = $this->createAvailabilities($availabilitiesData);
            if (!$createAvailabilities) {
                $CRUDAvailabilitiesTransaction = false;
            }

            // combine availabilities
            $minAvailabilityDateToCompare = date('Y-m-d', strtotime($dateToday . '-1 day'));
            $maxAvailabilityDateToCompare = date('Y-m-d', strtotime($endDate . '+1 day'));
            $combineAvailabilitiesDurations = $this->combineAvailabilitiesDurations($companyId, $workerId, $workerType, $minAvailabilityDateToCompare, $maxAvailabilityDateToCompare);
            if (!$combineAvailabilitiesDurations) {
                $CRUDAvailabilitiesTransaction = false;
            }
        } else {
            // delete assigned availability and update project workers timesheet
            $CRUDAssignedAvailabilityTransaction = $this->deleteAssignedAvailability($assignedWorkerId);
            $CRUDAssignedWorkersTransaction = $this->deleteAssignedWorkers($assignedWorkerId);

            // create availabilities
            $availabilitiesData = array(
                'worker_id' => $workerId,
                'company_id' => $companyId,
                'worker_type' => $workerType,
                'rate' => $workerRate,
                'min_availability_date' => $startDate,
                'max_availability_date' => $endDate,
            );
            $createAvailabilities = $this->createAvailabilities($availabilitiesData);
            if (!$createAvailabilities) {
                $CRUDAvailabilitiesTransaction = false;
            }

            // combine availabilities
            $minAvailabilityDateToCompare = date('Y-m-d', strtotime($startDate . '-1 day'));
            $maxAvailabilityDateToCompare = date('Y-m-d', strtotime($endDate . '+1 day'));
            $combineAvailabilitiesDurations = $this->combineAvailabilitiesDurations($companyId, $workerId, $workerType, $minAvailabilityDateToCompare, $maxAvailabilityDateToCompare);
            if (!$combineAvailabilitiesDurations) {
                $CRUDAvailabilitiesTransaction = false;
            }

           
        }

        /*========================================================================================================
        If there's an error or queries don't do their job, rollback!
        ========================================================================================================*/
        if (!$CRUDAvailabilitiesTransaction ||
            !$CRUDAssignedAvailabilityTransaction ||
            !$CRUDAssignedWorkersTransaction) {
            DB::rollBack();
            return response()->json(['error' => 'Server error.']);
        } else {
            $this->createUserAction("Removed " . $assignedWorkersData->first_name . " " . $assignedWorkersData->last_name . " from project " . $assignedWorkersData->project_name . ".");
            $mobileNotificationData = array(
                'worker_id' => $workerId,
                'message' => 'You have been removed from '. ucfirst($assignedWorkersData->project_name) . ".",
                'type' => 'worker',
                'link' => '/worker/tasks',
            );
            $this->createMobileNotification($mobileNotificationData);
            DB::commit();
            return response()->json(['success' => 'Data deleted successfully.'])->setStatusCode(201);
        }
    }

    public function fetchAssignedWorkersData($assignedWorkerId)
    {
        return AssignedAvailability::select(
            'assigned_availability.id',
            'assigned_availability.assigned_worker_id',
            'assigned_availability.worker_type',
            'assigned_availability.start_date',
            'assigned_availability.end_date',
            'assigned_availability.is_draft',
            'workers.id as worker_id',
            'workers.first_name',
            'workers.last_name',
            'projects.name as project_name')
            ->join('assigned_workers', 'assigned_workers.id', 'assigned_availability.assigned_worker_id')
            ->join('workers', 'workers.id', 'assigned_workers.worker_id')
            ->join('projects', 'projects.id', 'assigned_workers.project_id')
            ->where('assigned_availability.assigned_worker_id', $assignedWorkerId)
            ->first();
    }

    public function fetchWorkerRateFromAvailabilitiesTable($companyId, $workerId, $workerType)
    {
        return Availabilities::select(
            'availabilities.rate')
            ->where('availabilities.company_id', $companyId)
            ->where('availabilities.worker_id', $workerId)
            ->where('availabilities.worker_type', $workerType)
            ->orderBy('availabilities.updated_at', 'desc')
            ->withTrashed()
            ->first();
    }

    public function updateAssignedAvailability($assignedWorker_id, $assignedAvailabilityData, $endDate)
    {
        event(new UpdateAssignedAvailabilityEndDateEvent($assignedWorker_id, $endDate, 'remove'));
        return AssignedAvailability::where('assigned_availability.assigned_worker_id', $assignedWorker_id)->update($assignedAvailabilityData);
    }

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

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

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

    public function deleteAvailabilitiesByIds($availabilitiesIds)
    {
        return Availabilities::whereIn('availabilities.id', $availabilitiesIds)->delete();
    }

    public function deleteAssignedAvailability($assignedWorkerId)
    {
        event(new DeleteAssignedAvailabilityEvent($assignedWorkerId));
        return AssignedAvailability::where('assigned_availability.assigned_worker_id', $assignedWorkerId)->delete();
    }

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

    public function combineAvailabilitiesDurations($companyId, $workerId, $workerType, $minAvailabilityDateToCompare, $maxAvailabilityDateToCompare)
    {
        $availabilitiesDataToCombine = $this->fetchAvailabilitiesDataToCombine($companyId, $workerId, $workerType, $minAvailabilityDateToCompare, $maxAvailabilityDateToCompare);
        $availabilitiesDataToCombineCount = count($availabilitiesDataToCombine);
        if ($availabilitiesDataToCombineCount > 1) {
            $availabilitiesIdsToDelete = [];
            $availabilitiesToCombineFirstData = $availabilitiesDataToCombine[0];
            $availabilitiesToCombineFirstDataId = $availabilitiesToCombineFirstData->id;
            $availabilitiesToCombineFirstDataMinAvailabilityDate = $availabilitiesToCombineFirstData->min_availability_date;
            $availabilitiesToCombineLastData = $availabilitiesDataToCombine[$availabilitiesDataToCombineCount - 1];
            $availabilitiesToCombineLastDataMaxAvailabilityDate = $availabilitiesToCombineLastData->max_availability_date;
            $dateNow = date('Y-m-d');
            $dateDifference = date_diff(date_create($dateNow), date_create($availabilitiesToCombineLastDataMaxAvailabilityDate));
            $dateDifferenceInYears = $dateDifference->format('%y');
            $updateAvailabilitiesTransaction = true;

            foreach ($availabilitiesDataToCombine as $availabilities) {
                if ($availabilities->id != $availabilitiesToCombineFirstDataId) {
                    array_push($availabilitiesIdsToDelete, $availabilities->id);
                }
            }

            if ($dateDifferenceInYears == 10) {
                $availabilitiesData = array(
                    'max_availability_date' => date('Y-m-d', strtotime($availabilitiesToCombineFirstDataMinAvailabilityDate . '+10 years')),
                );
                $updateAvailabilitiesTransaction = $this->updateAvailabilities($availabilitiesToCombineFirstDataId, $availabilitiesData);
            } else {
                $availabilitiesData = array(
                    'max_availability_date' => $availabilitiesToCombineLastDataMaxAvailabilityDate,
                );
                $updateAvailabilitiesTransaction = $this->updateAvailabilities($availabilitiesToCombineFirstDataId, $availabilitiesData);
            }
            $deleteAvailabilitiesTransaction = $this->deleteAvailabilitiesByIds($availabilitiesIdsToDelete);
            return $updateAvailabilitiesTransaction && $deleteAvailabilitiesTransaction;
        }
        return true;
    }

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