<?php

namespace App\Modules\Company\Users;

use App\Models\Availabilities;
use App\Models\CompanyUser;
use App\Modules\Company\Projects;
use App\Models\ProjectSupervisors;
use App\Models\CompanyBurdenRate;
use App\Models\CompanyExperience;
use App\Traits\GoogleBucket;
use App\Traits\ZipcodeTraits;
use App\Traits\CreateUserActionTraits;
use App\Traits\MobileNumberValidatorTraits;
use App\Traits\GenerateAndValidateWorkerID;
use App\Traits\ValidatorTraits;
use App\Models\AssignedWorkers;
use App\Models\WorkAreaLists;
use App\Models\Worker;
use App\Models\WorkerFile;
use App\Models\Search;
use App\Models\WorkerExperience;
use App\Models\LoanApproved;
use Illuminate\Support\Facades\DB;
use App\Events\Company\UserRolesFlushedEvent;
use App\Events\Company\WorkerRateUpdatedEvent;
use App\Events\Company\CreateSearchWorkerEvent;
use App\Modules\RemoteStorage\GoogleBucketGateway;
use Illuminate\Support\Facades\Validator;

class UpdateUser
{
    use CreateUserActionTraits, MobileNumberValidatorTraits, ValidatorTraits, ZipcodeTraits, GenerateAndValidateWorkerID, GoogleBucket;

    public $googleBucket;
    public function __construct(GoogleBucketGateway $googleBucket)
    {
        $this->googleBucket = $googleBucket;
    }

    public function updateActiveStatus($payload, $id)
    {
        // update is_account_disabled column of conx_users table for a specific user
        $action = $payload->action;
        $description = '';
        if ($action == 'activate') {
            $worker_data = array(
                'is_account_disabled' => 'false',
            );
            Worker::where('id', $id)->update($worker_data);
            $description = "Activated the company user account of " . $payload->name;
        } else {
            $hasActiveProject = $this->checkIfHasActiveProject($id);
            if ($hasActiveProject) {
                return response()->json(['error' => ['User was assigned to a project and cannot be deactivated.']])->setStatusCode(201);
            }
            $worker_data = array(
                'is_account_disabled' => 'true',
            );
            $description = "Deactivated the company user account of " . $payload->name;
            Worker::where('id', $id)->update($worker_data);
        }

        // save user action
        $this->createUserAction($description);

        return response()->json(['success' => 'Data updated successfully.'])->setStatusCode(201);
    }

    public function checkIfHasActiveProject($id)
    {
        $project = Projects::where('project_manager_id', $id)->first();
        if ($project) {
            return true;
        }
        $projectSupervisor = ProjectSupervisors::where('user_id', $id)->first();
        if ($projectSupervisor) {
            return true;
        }
        return false;
    }

    public function checkIfAssignedAsManager($id){
        $project = Projects::where('project_manager_id', $id)->first();
        if ($project) {
            return true;
        }
        return false;
    }

    public function checkIfAssignedAsSupervisor($id){
        $projectSupervisor = ProjectSupervisors::where('user_id', $id)->first();
        if ($projectSupervisor) {
            return true;
        }
        return false;
    }

    public function checkIfPreviouslyAssignedAsWorker($id){
        $isWorker = CompanyUser::where('worker_id', $id)
            ->where('role_id', 5)
            ->first();

        if ($isWorker) {
            return true;
        }
        return false;
    }

    public function checkActiveBorrowWorkerTransaction($workerID){
        return LoanApproved::select(
            'loan_approved_histories.loaner_company_id',
            'loan_approved_histories.borrower_company_id',
            'loan_approved_histories.date_from',
            'loan_approved_histories.date_to',
            'loan_approved_histories.worker_id',
        )
        ->whereRaw('loan_approved_histories.date_to >= CURDATE()')
        ->where('loan_approved_histories.worker_id', $workerID)
        ->distinct()
        ->first();
    }

    public function updateDetails($payload, $id)
    {

        // start transaction
        DB::beginTransaction();

        // worker data
        $companyId = $this->getCurrentUser()->company_id;
        $firstName = ucwords($payload->first_name);
        $lastName = ucwords($payload->last_name);
        $emailAddress = strtolower($payload->email_address);
        $profilePicture = $payload->profile_picture;
        $phoneNum = '+1' . str_replace(array('-', '(', ')', ' '), '', $payload->phone_num);
        $streetAddress = $payload->street_address;
        $city = $payload->city;
        $state = $payload->state;
        $zipcode = $payload->zipcode;
        $hourlyRate = $payload->hourly_rate;
        $dateHired = $payload->date_hired;
        $dateHired = date('Y-m-d', strtotime($dateHired));
        $additionalDetails = $payload->additional_details;
        $additionalDetails = $additionalDetails == 'null' || $additionalDetails == ''? '' : $additionalDetails;
        $willingDistance = $payload->willing_distance;

        // worker experiences data
        $expertise = $payload->expertise;
        $levelOfExperience = $payload->level_of_experience;
        $workerType = 'worker';
        $otherExperience = $payload->other_experience;
        $otherExpertise = $payload->other_expertise;
        // worker areas of work data
        $areasOfWork = is_array($payload->areas_of_work)? $payload->areas_of_work : explode(',', $payload->areas_of_work);
        // role data
        $roles = is_array($payload->roles) ? $payload->roles : explode(',', $payload->roles);

        // validate query param
        $rules = array();
        $removedWorkerRole = false;
        if (in_array(5, $roles)) { // 5 = Worker
            $rules = array(
                'first_name' => 'required|max:50',
                'last_name' => 'required|max:50',
                'email_address' => 'required|unique:workers,email_address,' . $id . '|max:191',
                'phone_num' => 'required|max:50',
                // 'street_address' => 'required|max:50',
                // 'city' => 'required|max:50',
                // 'state' => 'required|max:50',
                'zipcode' => 'required|string|min:5|max:5',
                'hourly_rate' => 'required|numeric|min:1|max:10000',
                // 'date_hired' => 'required',
                'expertise' => 'required',
                'level_of_experience' => 'required',
                'willing_distance' => 'required',
            );
        } else {
            $rules = array(
                'first_name' => 'required|max:50',
                'last_name' => 'required|max:50',
                'email_address' => 'required|unique:workers,email_address,' . $id . '|max:191',
                'phone_num' => 'required|max:50',
                // 'street_address' => 'required|max:50',
                // 'city' => 'required|max:50',
                // 'state' => 'required|max:50',
                'zipcode' => 'required|string|min:5|max:5',
                'hourly_rate' =>'required|numeric|min:1|max:10000',
                // 'date_hired' => 'required',
            );
            $removedWorkerRole = true;
        }

        $validate = $this->validateRequest($payload, $rules);
        if ($validate) {
            return $validate;
        }

        $validate = $this->validatePhoneNumber($payload->phone_num);
        if ($validate) {
            return $validate;
        }

        $validate = $this->validateZipcodeIfExisting($payload->zipcode);
        if ($validate) {
            return $validate;
        }

        //Check if a current worker and has active loan/borrow transaction
        $isPreviousWorker = $this->checkIfPreviouslyAssignedAsWorker($id);
        $hasActiveTransaction = $this->checkActiveBorrowWorkerTransaction($id);
        $isWorkerCurrentlyAssignedToAProject = $this->checkIfWorkerAssignedToAProject($id);


        if($isPreviousWorker && $removedWorkerRole){
            $workerRoleID = 5;
            if($hasActiveTransaction){
                if (!in_array($workerRoleID, $roles)) { 
                    return response()->json(['errors' => ["Unable to proceed on removing worker role. The worker currently has active transactions."]])->setStatusCode(201); //actively loaned out
                }
            }
            if($isWorkerCurrentlyAssignedToAProject){  //actively assigned as worker on a project
                if (!in_array($workerRoleID, $roles)) { 
                    return response()->json([
                        'errors' => $isWorkerCurrentlyAssignedToAProject->first_name . ' ' .
                        $isWorkerCurrentlyAssignedToAProject->last_name . ' (' .
                        $isWorkerCurrentlyAssignedToAProject->worker_id . ') was assigned to ' .
                        $isWorkerCurrentlyAssignedToAProject->project_name . ' ' .
                        'and cannot remove role as worker.',
                    ])->setStatusCode(201);
                }
            }
        }

        // Check if user is assigned as project manager
        if($this->checkIfAssignedAsManager($id)){
            $managerRoleId = 2;

            if (!in_array($managerRoleId, $roles)) { 
                return response()->json(['errors' => ["Unable to procced. User is currently assigned as Project Manager."]])->setStatusCode(201);
            }
        }

        // Check if user is assigned as project supervisor
        if($this->checkIfAssignedAsSupervisor($id)){
            $supervisorRoleId = 3;

            if (!in_array($supervisorRoleId, $roles)) { 
                return response()->json(['errors' => ["Unable to procced. User is currently assigned as Project Supervisor."]])->setStatusCode(201);
            }
        }


        // validation for company with at least 1 company owner
        $validateCompanyOwnerCount = $this->validateCompanyOwnerCount();
        if ($validateCompanyOwnerCount < 1) {
            return response()->json(['errors' => 'Company should have at least 1 company owner.'])->setStatusCode(201);
        }

        // Certificates file name validation
        if($payload->files_names){

            $filesValidator = $this->validateCertificateFiles($payload);
            if($filesValidator){
                return $filesValidator;
            }

            $fileNamesValidator = $this->validateCerficatesName($payload, $id);
            if($fileNamesValidator){
                return $fileNamesValidator;
            }
        }

        // profile picture
        $hasProfile = $profilePicture == 'null' || $profilePicture == ''?  false : true;
        $profilePicture = $hasProfile? $payload->profile_picture : null;
        if ($hasProfile) {

            $isBase64Profile = $payload->isBase64Profile == false || $payload->isBase64Profile == 'false'? false : true;
            if(!$isBase64Profile){
                $this->removeUploadedFile($payload->previousProfileLink, $this->googleBucket); //remove the previous link
            }

            $image_64 = $profilePicture;
            $extension = explode('/', explode(':', substr($image_64, 0, strpos($image_64, ';')))[1])[1]; // .jpg .png
            $image = explode(',', $image_64)[1];
            $filePath = $this->createUniqueImageNameJobseeker($companyId, $extension);
            $this->uploadBase64Image($image, $filePath, $this->googleBucket);

            $profilePicture = $filePath;
        }


        //re-check if the user to be updated has company_worker_id || worker_id
        $updatedToBeWorker = in_array(5, $roles);// 5 = Worker
        $workerPreviousData = $this->fetchWorkerData($id);
        if($updatedToBeWorker){

            if(!$workerPreviousData->worker_id){
                $workerCompanyID = $this->generateWorkerId($companyId);
            }else{
                $workerCompanyID = $workerPreviousData->worker_id;
            }

            $workerData = array(
                'worker_id' => $workerCompanyID,
                'first_name' => $firstName,
                'last_name' => $lastName,
                'email_address' => $emailAddress,
                'phone_num' => $phoneNum,
                'street_address' => $streetAddress,
                'city' => $city,
                'state' => $state,
                'zipcode' => $zipcode,
                'flat_rate' => $hourlyRate,
                'date_hired' => $dateHired,
                'additional_details' => $additionalDetails,
                'distance' => $willingDistance,
            );

            if($payload->files_names){
                $fileNames = $payload->files_names;
                $indexCount = 0;
                foreach($fileNames as $key => $name){
                    $filePayloadName = 'files.'. $indexCount;
                    $resumeExtension = $payload->file($filePayloadName)->extension();
                    $resumeFilePathName = $this->createUniqueFileName(1, $resumeExtension);
    
                    $this->uploadNewFile($payload, $resumeFilePathName, $filePayloadName, $this->googleBucket);
                    $indexCount++;
                    $newCertificate = array(
                        'worker_id' => $id,
                        'name' => str_replace ( '.'.$resumeExtension, '', $name), //remove the extension '.pdf'
                        'location' => $resumeFilePathName
                    );
                    WorkerFile::create($newCertificate);
                }
            }

        }else{
            $workerData = array(
                'first_name' => $firstName,
                'last_name' => $lastName,
                'email_address' => $emailAddress,
                'phone_num' => $phoneNum,
                'street_address' => $streetAddress,
                'city' => $city,
                'state' => $state,
                'zipcode' => $zipcode,
                'flat_rate' => $hourlyRate,
                'date_hired' => $dateHired,
                'additional_details' => $additionalDetails,
            );
        }

        if($hasProfile){
            $workerData['profile_picture'] = $profilePicture; //add profile link
        }
        // process update user data
        $userTransaction = $this->updateWorker($id, $workerData);

        // Process company Experience
        $companyWorkerExperienceTransaction = 1;
        $workerAvailabilityTransaction = 1;
        if ($updatedToBeWorker) { 
            // preparing worker experiences data to be saved on database
            $this->removeDuplicateBurdenRate($levelOfExperience); //to remove duplicate company burden rate on specific company experience bug - 

            if ($levelOfExperience == 0 || $otherExperience) {
                $checkIfNewExpeExisted = $this->getCompanyExperienceAndBurdenData($otherExperience, $companyId); //validate the name first if exisintng

                if($checkIfNewExpeExisted){ //already has the data, get only the commpany_experience_id and burden rate id
                    $levelOfExperience = $checkIfNewExpeExisted->company_experience_id;
                    $burdenRateId = $checkIfNewExpeExisted->company_burden_rate_id;
                    $note = 'Get an existing company experience, from other proccess';
                }else{
                    $newExperience = CompanyExperience::create([
                        'company_id' => $companyId,
                        'name' => $otherExperience,
                    ]);
        
                    $newBurden = CompanyBurdenRate::create([
                        'company_experience_id' => $newExperience->id,
                        'company_id' => $companyId,
                        'burden_rate' => 0.0,
                        'burden_rate_type' => 'percent',
                    ]);

                    $levelOfExperience = $newExperience->id;
                    $burdenRateId = $newBurden->id;
                    $note = 'Create new company experience, from other proccess';
                }

            }else{
                //the selected experience is already existing, get the experience id and its burden rate id
                $checkIfNewExpeExisted = $this->getCompanyExperienceAndBurdenDataUsingId($levelOfExperience, $companyId);
                $levelOfExperience = $checkIfNewExpeExisted->company_experience_id;
                $burdenRateId = $checkIfNewExpeExisted->company_burden_rate_id;
                $note = 'Get an existing company experience';
            }

            //Process Worker Experience
            $checkWorkerExperience = $this->checkWorkerExperience($id);
            if (!$checkWorkerExperience) {  // no experience
                $newWorkerExperienceData = array(
                    'experience_id' => $levelOfExperience,
                    'expertise_id' => $expertise,
                    'is_worker' => 'true',
                    'experience' => $payload->other_experience,
                    'expertise' => $payload->other_expertise,
                    'burden_rate_id' => $burdenRateId,
                    'worker_id' => $id,
                );
                $note1 = 'Create new worker experience';
                $companyWorkerExperienceTransaction = $this->saveWorkerExperience($newWorkerExperienceData);
            } else {
                $newWorkerExperienceData = array(
                    'experience_id' => $levelOfExperience,
                    'expertise_id' => $expertise,
                    'is_worker' => 'true',
                    'experience' => $payload->other_experience,
                    'expertise' => $payload->other_expertise,
                    'burden_rate_id' => $burdenRateId
                );
                $note1 = 'Update worker experience';
                $companyWorkerExperienceTransaction = $this->updateWorkerExperience($id, $newWorkerExperienceData);
            }

            // worker areas
            $this->deleteCurrentWorkerAreas($id);
            foreach ($areasOfWork as $area_of_work) {
                $worker_areas_of_work_data = array(
                    'work_area_id' => $area_of_work,
                    'worker_id' => $id,
                    'worker_type' => $workerType,
                );
                $companyWorkAreaListTransaction = $this->saveWorkerWorkAreas($worker_areas_of_work_data);
                if (!$companyWorkAreaListTransaction) {
                    DB::rollBack();
                    return response()->json(['error' => 'Something went wrong in updating areas of work.']);
                }
            }

            // check worker availability data
            $workerAvailabilityData = $this->checkWorkerIfAlreadyHaveAnAvailabilityData($id);
            if ($workerAvailabilityData->isEmpty()) {
                // preparing worker availability data to be saved on database
                $minAvailabilityDate = date("Y-m-d");
                $maxAvailabilityDate = date('Y-m-d', strtotime('+10 years'));
                $workerAvailabilityData = array(
                    'worker_id' => $id,
                    'company_id' => $companyId,
                    'worker_type' => 'inhouse',
                    'rate' => $hourlyRate,
                    'min_availability_date' => $minAvailabilityDate,
                    'max_availability_date' => $maxAvailabilityDate,
                );
                $companyWorkerAvailabilityTransaction = $this->saveWorkerAvailability($workerAvailabilityData);
                if (!$companyWorkerAvailabilityTransaction) {
                    DB::rollBack();
                    return response()->json(['error' => 'Something went wrong in updating workers availability dates.']);
                }
            }else{
                // preparing worker availability data to be saved on database
                $worker_availability_data = array(
                    'rate' => $hourlyRate,
                );
                $workerAvailabilityTransaction = $this->updateWorkerAvailabilityRate($id, $worker_availability_data);
            }
        } else {

            // check worker availability data
            $workerAvailabilityData = $this->checkWorkerIfAlreadyHaveAnAvailabilityData($id);
            if (!$workerAvailabilityData->isEmpty()) {
                $deleteWorkerAvailabilitiesTransaction = $this->deleteWorkerAvailabilities($id);
                if (!$deleteWorkerAvailabilitiesTransaction) {
                    DB::rollBack();
                    return response()->json(['error' => 'Something went wrong in deleting workers availability dates.']);
                }
            }
        }

        // preparing user role to be saved on database
        $deleteCompanyUserRoleTransaction = $this->deleteCompanyUserRole($id);
        event(new UserRolesFlushedEvent($roles, $id));

        foreach ($roles as $role) {
            $companyUserRole = array(
                'worker_id' => $id,
                'company_id' => $companyId,
                'role_id' => $role,
                'availability_status' => $role == 5 ? 'reserved' : '',
            );
            $companyUserRoleTransaction = $this->saveCompanyUser($companyUserRole);
            if (!$companyUserRoleTransaction) {
                DB::rollBack();
                return response()->json(['error' => 'Something went wrong in updated users role.']);
            }
        }

        if(!$updatedToBeWorker){
            //if has search data, it should be haulted
            $this->updateSearchDataToHaulted($id);
        }

        // if there's an error or queries don't do their job, rollback!
        if (!$userTransaction ||
            !$companyWorkerExperienceTransaction ||
            !$workerAvailabilityTransaction ||
            !$deleteCompanyUserRoleTransaction) {
            DB::rollBack();
            return response()->json(['error' => 'Something went wrong.',
            'userTransaction' => $userTransaction,
                'companyWorkerExperienceTransaction' => $companyWorkerExperienceTransaction,
                'workerAvailabilityTransaction' => $workerAvailabilityTransaction,
                'deleteCompanyUserRoleTransaction' => $deleteCompanyUserRoleTransaction,
            ]);
        } else {

            event(new WorkerRateUpdatedEvent($id, $companyId));

            if ($updatedToBeWorker) {
                event(new CreateSearchWorkerEvent($companyUserRoleTransaction)); //Event for creating search data for worker
            }
            DB::commit();
            return response()->json(['success' => 'Data updated successfully.'])->setStatusCode(201);
        }
    }

    public function fetchWorkerData($id){
        return Worker::where('id', $id)->first();
    }

    public function updateWorker($id, $workerData)
    {
        return Worker::where('id', $id)->update($workerData);
    }

    public function updateWorkerAvailabilityRate($id, $worker_availability_data)
    {
        return Availabilities::where('worker_id', $id)
            ->where('worker_type', 'inhouse')
            ->update($worker_availability_data);
    }

    public function checkWorkerExperience($id)
    {
        return WorkerExperience::where('worker_id', $id)->first();
    }

    public function saveWorkerExperience($worker_experiences_data)
    {
        return WorkerExperience::create($worker_experiences_data);
    }

    public function updateWorkerExperience($id, $worker_experiences_data)
    {
        return WorkerExperience::where('worker_id', $id)->update($worker_experiences_data);
    }

    public function deleteCurrentWorkerAreas($id)
    {
        return WorkAreaLists::where('worker_id', $id)
            ->where('work_area_lists.worker_type', 'worker')
            ->delete();
    }

    public function saveWorkerWorkAreas($worker_areas_of_work_data)
    {
        return WorkAreaLists::create($worker_areas_of_work_data);
    }

    public function checkIfWorkerAssignedToAProject($id)
    {
        return AssignedWorkers::select(
            'projects.name as project_name',
            'assigned_availability.end_date',
            'workers.first_name',
            'workers.last_name',
            'workers.worker_id'
        )
        ->join('assigned_availability', 'assigned_availability.assigned_worker_id', '=', 'assigned_workers.id')
        ->join('workers', 'workers.id', '=', 'assigned_workers.worker_id')
        ->join('projects', 'projects.id', '=', 'assigned_workers.project_id')
        ->whereRaw('assigned_availability.end_date >= CURDATE()')
        ->where('assigned_workers.worker_id', $id)
        ->first();
    }

    public function deleteWorkerAvailabilities($id)
    {
        return Availabilities::where('worker_id', $id)
            ->where('worker_type', 'inhouse')
            ->delete();
    }

    public function deleteCompanyUserRole($id)
    {
        return CompanyUser::where('worker_id', $id)->delete();
    }

    public function saveCompanyUser($company_user_data)
    {
        return CompanyUser::create($company_user_data);
    }

    public function checkWorkerIfAlreadyHaveAnAvailabilityData($workerId)
    {
        return Availabilities::where('worker_id', $workerId)->limit(1)->get();
    }

    public function saveWorkerAvailability($worker_availability_data)
    {
        return Availabilities::create($worker_availability_data);
    }

    public function validateCompanyOwnerCount()
    {
        $companyUser = CompanyUser::where('company_id', $this->getCurrentUser()->company_id)
            ->where('role_id', 1) // 1 = Owner
            ->get();
        return $companyUser->count();
    }

    public function checkEmail($email_address)
    {
        $data = Worker::where('email_address', $email_address)->first();
        return ($data) ? $email_address : false;
    }

    public function removeDuplicateBurdenRate($companyExperienceID){
        $companyBurdenRate = CompanyBurdenRate::where('company_experience_id', $companyExperienceID)->get();

        if($companyBurdenRate->count() > 1){
            $count = 0;
            foreach($companyBurdenRate as $burdemRate){
                if($count > 0){  //don't remove the first company rate
                    CompanyBurdenRate::where('id', $burdemRate->id)->delete();
                }
                $count++;
            }
        }
    }

    public function getCompanyExperienceAndBurdenData($name, $companyID){
       if($name != 'Others' || $name != 'Other'){
            return CompanyExperience::select(
                'company_experiences.id as company_experience_id',
                'company_experiences.name',
                'company_burden_rate.company_id',
                'company_burden_rate.id as company_burden_rate_id',
            )
            ->join('company_burden_rate', 'company_burden_rate.company_experience_id', 'company_experiences.id')
            ->where('company_experiences.name', $name)
            ->where('company_experiences.company_id', $companyID)
            ->first();
        }
        return null;
    }

    public function getCompanyExperienceAndBurdenDataUsingId($companyExperienceID, $companyID){
        return CompanyExperience::select(
            'company_experiences.id as company_experience_id',
            'company_experiences.name',
            'company_burden_rate.company_id',
            'company_burden_rate.id as company_burden_rate_id',
        )
        ->join('company_burden_rate', 'company_burden_rate.company_experience_id', 'company_experiences.id')
        ->where('company_experiences.id', $companyExperienceID)
        ->where('company_experiences.company_id', $companyID)
        ->first();
    }

    public function updateSearchDataToHaulted($workerID){
        Search::where('worker_id', $workerID)->update(['status' => 'Haulted']);
    }   
}
