<?php
namespace App\Modules\Mobile\Worker;

use App\Models\Worker;
use App\Models\WorkerFile;
use App\Models\WorkAreaLists;
use App\Models\Availabilities;
use App\Models\WorkerExperience;
use App\Models\CompanyBurdenRate;
use App\Models\CompanyExperience;
use App\Events\Mobile\CreateSearchDataForInvitedWorkerEvent;
use App\Traits\MobileNumberValidatorTraits;
use App\Traits\ValidatorTraits;
use App\Traits\ZipcodeTraits;
use App\Traits\GoogleBucket;
use App\Modules\RemoteStorage\GoogleBucketGateway;

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Intervention\Image\Facades\Image;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Carbon\Carbon;

class Invitation
{

    use ValidatorTraits, MobileNumberValidatorTraits, ZipcodeTraits, GoogleBucket;

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

    public function getCode($payload)
    {

        //return null = no data
        if (!$payload->token) {
            return null;
        }

        $token = $payload->token;

        $workerData = Worker::select(
                'workers.id',
                'workers.worker_id',
                'workers.email_address',
                'workers.phone_num',
                'workers.first_name',
                'workers.last_name',
                'workers.profile_picture',
                'workers.street_address',
                'workers.city',
                'workers.state',
                'workers.zipcode',
                'workers.flat_rate',
                'workers.date_hired',
                'company_users.availability_status',
                'company_users.company_id',
                'workers.additional_details',
                'workers.distance',
                'worker_experiences.experience_id',
                'company_experiences.name as experience_name',
                'worker_experiences.experience as experience',
                'worker_experiences.expertise_id',
                'worker_experiences.expertise as expertise',
                'expertises.name as expertise_name')
                ->join('company_users', 'company_users.worker_id', '=', 'workers.id')
                ->leftJoin('worker_experiences', 'worker_experiences.worker_id', '=', 'workers.id')
                ->leftJoin('company_experiences', 'company_experiences.id', '=', 'worker_experiences.experience_id')
                ->leftJoin('expertises', 'expertises.id', '=', 'worker_experiences.expertise_id')
                ->with('work_area_lists:work_area_id,worker_id,name')
                ->with('roles:worker_id,role_id,name')
                ->where('workers.email_verification_code', $token)
                ->first(1);

        if ($workerData) {
            return [
                'workerData' => $workerData,
                'companyExperience' => $this->getCompanyExperienceForInvitation($workerData->company_id),
            ];
        }

        return null;
    }

    public function getCompanyExperienceForInvitation($companyID){
        $companyExperiences =  CompanyExperience::where('company_id', $companyID)->get();
        $holdOther = false;
        //make the 'Other or Others' item, as the last item
        foreach($companyExperiences as $key => $experience){
            if($experience->name == 'Other' || $experience->name == 'Others'){
                $holdOther = $experience;
                unset($companyExperiences[$key]);
            }
        }
        
        if($holdOther){
            $companyExperiences->push($holdOther); //set the other as the last item
        }
        return $companyExperiences;
    }

    public function create($payload)
    {

        $email_address = $payload->email_address;

         /*========================================================================================================
        Validate Query Params
        ========================================================================================================*/
        $rules = array(
            'first_name' => 'required|max:50',
            'last_name' => 'required|max:50',
            'email_address' => 'required|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',
            'expertise' => 'required',
            'level_of_experience' => 'required',
            'company_id' => 'required',
            'willing_distance' => 'required',
            'files_names' => 'required',
        );

        $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;
        }

        $token = $payload->token;
        $validation = Worker::where('email_verification_code', $token)->first();

        if (!$validation) {
            return response()->json(['errors' => ["Sorry, token doesn't exist."]])->setStatusCode(201);
        }

        // Check if already has a worker-data
        $oldWorkerData = $this->checkWorker($email_address);
        if (!$oldWorkerData) {
            return response()->json(['errors' => ["Sorry, worker doesn't exist."]])->setStatusCode(201);
        }

        // Certificates file name validation
        $messages = array('files_names.*.max' => 'File name should not exceed 200 characters.', 'files_names.*.min' => 'File name should atleast has 1 character.');
        $rules = array('files_names.*' => 'required|min:1|max:200');
        $validator = Validator::make($payload->all(), $rules, $messages);

        if ($validator->fails()){
           if ($validator->fails()){
               return [
                   'errors' => $validator->errors()->first()
               ];
           }
        }

        DB::beginTransaction();

        $first_name = ucfirst($payload->first_name);
        $last_name = ucfirst($payload->last_name);
        $profile_picture = $payload->profile_picture;
        $phone_num = '+1' . str_replace(array('-', '(', ')', ' '), '', $payload->phone_num);
        $street_address = $payload->street_address;
        $city = $payload->city;
        $state = $payload->state;
        $zipcode = $payload->zipcode;
        $additional_details = $payload->additional_details;
        $additional_details = $additional_details == 'null' || $additional_details == 'undefined'? '' : $additional_details;
        $password = Hash::make($payload->password);
        $company_id = $oldWorkerData->company_id;
        $willingDistance = $payload->willing_distance;

        /*========================================================================================================
        Prepare data and create User
        ========================================================================================================*/
        $user_data = [
            'first_name' => $first_name,
            'last_name' => $last_name,
            'password' => $password,
            'email_address' => $email_address,
            'email_verified_at' => Carbon::now(),
            'profile_picture' => $profile_picture,
            'is_active' => 'true',
            'enable_2fa' => 'false',
            'phone_num' => $phone_num,
            'email_verification_code' => null,
            'is_account_disabled' => 'false',
            'street_address' => $street_address,
            'city' => $city,
            'state' => $state,
            'zipcode' => $zipcode,
            'additional_details' => $additional_details,
            'distance' => $willingDistance,
        ];

        $workerData = tap(Worker::where('email_address', $email_address))->update($user_data)->first();

        /*========================================================================================================
        worker experiences data
        ========================================================================================================*/
        $expertise = $payload->expertise;
        $level_of_experience = $payload->level_of_experience;
        $worker_type = 'worker';

        /*========================================================================================================
        worker areas of work data
        ========================================================================================================*/
        $areas_of_work = $payload->areas_of_work;
        $areas_of_work = is_array($payload->areas_of_work)? $payload->areas_of_work : explode(',', $payload->areas_of_work);

        /*========================================================================================================
        preparing worker experiences data to be saved on database
        ========================================================================================================*/

        $burdenRateTransaction = $this->isBurdenRateExisting($company_id, $level_of_experience);
        $burdenRate = $burdenRateTransaction->burden_rate;
        $burdenRateType = $burdenRateTransaction->burden_rate_type;

        if(!$burdenRateTransaction){
            $workerBurdenRate = array(
                'company_id' => $company_id,
                'company_experience_id' => $level_of_experience,
                'burden_rate' => 10,
                'burden_rate_type' => 'percent',
            );
            $burdenRateTransaction = $this->createBurdenRate($workerBurdenRate);
        }

        /* BURDEN RATE */
        $worker_experiences_data = array(
            'worker_id' => $workerData->id,
            'experience_id' => $level_of_experience,
            'expertise_id' => $expertise,
            'is_worker' => 'true',
            'experience' => $payload->other_experience,
            'expertise' => $payload->other_expertise,
            'burden_rate_id' => $burdenRateTransaction->id
        );

        $companyWorkerExperienceTransaction = WorkerExperience::create($worker_experiences_data);


        /*========================================================================================================
        worker areas
        ========================================================================================================*/
        if (is_array($areas_of_work) || is_object($areas_of_work)) {
            foreach ($areas_of_work as $area_of_work) {
                $worker_areas_of_work_data = array(
                    'work_area_id' => $area_of_work,
                    'worker_id' => $workerData->id,
                    'worker_type' => $worker_type,
                );
                $workerWorkAreaListTransaction = WorkAreaLists::create($worker_areas_of_work_data);
            }
        } else {
            return $areas_of_work;
        }

        /*========================================================================================================
        preparing worker availability data to be saved on database
        ========================================================================================================*/
        $hourlyRate = $workerData->flat_rate;
        if($burdenRate || $burdenRateType){
            $loadedRate = $burdenRateType == 'percent'? $hourlyRate + (($burdenRate * 0.01) * $hourlyRate): $hourlyRate + $burdenRate;
        }
        
        $min_availability_date = date("Y-m-d");
        $max_availability_date = date('Y-m-d', strtotime('+10 years'));
        $worker_availability_data = array(
            'worker_id' => $workerData->id,
            'company_id' => $company_id,
            'worker_type' => 'inhouse',
            'rate' => $loadedRate? $loadedRate: $workerData->rate,
            'min_availability_date' => $min_availability_date,
            'max_availability_date' => $max_availability_date,
        );
        $workerAvailabilityTransaction = $this->saveWorkerAvailability($worker_availability_data);


        //CREATE WORKER FILES
        $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' => $workerData->id,
                'name' => str_replace ( '.'.$resumeExtension, '', $name), //remove the extension '.pdf'
                'location' => $resumeFilePathName
            );
            WorkerFile::create($newCertificate);
        }

        /*========================================================================================================
        If there's an error or queries don't do their job, rollback!
        ========================================================================================================*/
        if (!$workerWorkAreaListTransaction && !$workerAvailabilityTransaction && !$companyWorkerExperienceTransaction && !$burdenRateTransaction) {
            DB::rollBack();
            return response()->json(
                ['error' => 'Server error.',
                    'company_work_area_list_result' => $workerWorkAreaListTransaction,
                ]);

        } else {
            event(new CreateSearchDataForInvitedWorkerEvent($workerData->id)); //Event for creating search data on newly accepted worker
            DB::commit();
            return response()->json(['success' => true, 'message' => 'Data updated successfully.'])->setStatusCode(201);
        }

    }

    public function generateWorkerId($company_id, $date_hired)
    {
        $dateNow = date("mdy");
        $latestWorkerId = $this->getLatestWorkerId($company_id, $date_hired);
        $newWorkerId = $this->generateNewWorkerId($latestWorkerId, $company_id, $dateNow);
        return $newWorkerId;
    }
    public function getLatestWorkerId($company_id, $date_hired)
    {
        $date_hired = date('Y-m-d', strtotime($date_hired));
        $employee_data = Worker::select('workers.worker_id')
            ->join('company_users', 'company_users.worker_id', '=', 'workers.id')
            ->where('company_users.company_id', $company_id)
            ->where('workers.date_hired', $date_hired)
            ->orderBy('company_users.id', 'desc')
            ->limit(1)
            ->get();
        if ($employee_data->isEmpty()) {
            return sprintf("%04d", $company_id) . '-' . $this->formatDateHiredForWorkerId($date_hired) . '-' . '0000';
        } else {
            return $employee_data[0]->worker_id;
        }
    }
    public function formatDateHiredForWorkerId($date_hired)
    {
        return date('mdy', strtotime($date_hired));
    }

    public function generateNewWorkerId($latestWorkerId, $company_id, $date_hired)
    {
        $latestWorkerIdArray = str_split($latestWorkerId, 12);
        $workerId = (int) $latestWorkerIdArray[1];
        $workerId++;
        return sprintf("%04d", $company_id) . '-' . $this->formatDateHiredForWorkerId($date_hired) . '-' . sprintf("%04d", $workerId);
    }
    public function saveWorkerAvailability($worker_availability_data)
    {
        return Availabilities::create($worker_availability_data);
    }

    public function checkWorker($email_address)
    {
        return Worker::select(
            'company_users.company_id', 
            'workers.id'
        )
        ->join('company_users', 'company_users.worker_id', 'workers.id')
        ->where('workers.email_address', $email_address)
        ->first();
    }

    public function accept($payload)
    {

        $email_address = $payload->email_address;
        /*========================================================================================================
        Validate Query Params
        ========================================================================================================*/
        $rules = array(
            'first_name' => 'required|max:50',
            'last_name' => 'required|max:50',
            'email_address' => 'required|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',
            'expertise' => 'required',
            'level_of_experience' => 'required',
            'company_id' => 'required',
            'willing_distance' => 'required',
            'files_names' => 'required',
        );

        $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;
        }

        $token = $payload->token;
        $validation = Worker::where('email_verification_code', $token)->first();

        if (!$validation) {
            return response()->json(['errors' => ["Sorry, token doesn't exist."]])->setStatusCode(201);
        }

        // Check if already has a worker-data
        $oldWorkerData = $this->checkWorker($email_address);
        if (!$oldWorkerData) {
            return response()->json(['errors' => ["Sorry, worker doesn't exist."]])->setStatusCode(201);
        }

        // Certificates file name validation
        $messages = array('files_names.*.max' => 'File name should not exceed 200 characters.', 'files_names.*.min' => 'File name should atleast has 1 character.');
        $rules = array('files_names.*' => 'required|min:1|max:200');
        $validator = Validator::make($payload->all(), $rules, $messages);

        if ($validator->fails()){
           if ($validator->fails()){
               return [
                   'errors' => $validator->errors()->first()
               ];
           }
        }

        DB::beginTransaction();

        $first_name = ucfirst($payload->first_name);
        $last_name = ucfirst($payload->last_name);
        $profile_picture = $payload->profile_picture;
        $phone_num = '+1' . str_replace(array('-', '(', ')', ' '), '', $payload->phone_num);
        $street_address = $payload->street_address;
        $city = $payload->city;
        $state = $payload->state;
        $zipcode = $payload->zipcode;
        $additional_details = $payload->additional_details;
        $additional_details = $additional_details == 'null' || $additional_details == 'undefined'? '' : $additional_details;
        $password = Hash::make($payload->password);
        $company_id = $oldWorkerData->company_id;
        $willingDistance = $payload->willing_distance;

        /*========================================================================================================
        Profile picture
        ========================================================================================================*/
        if ($profile_picture != '') {
            $image_64 = $profile_picture;
            $extension = explode('/', explode(':', substr($image_64, 0, strpos($image_64, ';')))[1])[1]; // .jpg .png
            $replace = substr($image_64, 0, strpos($image_64, ',') + 1);
            // find substring fro replace here eg: data:image/png;base64,
            $image = str_replace($replace, '', $image_64);
            $image = str_replace(' ', '+', $image);
            $imageName = Str::random(10) . '.' . $extension;
            // resize image
            $photo = Image::make(base64_decode($image))
                ->resize(120, 120)
                ->encode($extension, 90);
            $profile_picture = 'data:image/' . $extension . ';base64,' . base64_encode($photo);
        }


        /*========================================================================================================
        Prepare data and create User
        ========================================================================================================*/
        $user_data = [
            'first_name' => $first_name,
            'last_name' => $last_name,
            'password' => $password,
            'email_address' => $email_address,
            'email_verified_at' => Carbon::now(),
            'profile_picture' => $profile_picture,
            'is_active' => 'true',
            'enable_2fa' => 'false',
            'phone_num' => $phone_num,
            'email_verification_code' => null,
            'is_account_disabled' => 'false',
            'street_address' => $street_address,
            'city' => $city,
            'state' => $state,
            'zipcode' => $zipcode,
            'additional_details' => $additional_details,
            'distance' => $willingDistance,
        ];

        $workerData = tap(Worker::where('email_address', $email_address))->update($user_data)->first();

        /*========================================================================================================
        worker experiences data
        ========================================================================================================*/
        $expertise = $payload->expertise;
        $level_of_experience = $payload->level_of_experience;
        $worker_type = 'worker';

        /*========================================================================================================
        worker areas of work data
        ========================================================================================================*/
        $areas_of_work = $payload->areas_of_work;
        $areas_of_work = is_array($payload->areas_of_work)? $payload->areas_of_work : explode(',', $payload->areas_of_work); //formdata turn array into string, explode to get it back into array

        /*========================================================================================================
        preparing worker experiences data to be saved on database
        ========================================================================================================*/

        // check worker experience if has one
        $workerExperienceData = $this->checkWorkerExperience($workerData->id);

        $test = true;
        if($workerExperienceData && !($level_of_experience == $workerExperienceData->experience_id)){
            //different epxerience meaning change the worker experience
            $newCompanyExperienceBurdenRateData = $this->getCompanyBurdenRateData($level_of_experience);

            $newWorkerExperienceData = array(
                'experience_id' => $level_of_experience,
                'expertise_id' => $expertise,
                'is_worker' => 'true',
                'experience' => $payload->other_experience,
                'expertise' => $payload->other_expertise,
                'burden_rate_id' => $newCompanyExperienceBurdenRateData->id
            );

            $workerExperienceTransaction = $this->updateWorkerExperience($workerData->id, $newWorkerExperienceData);
            $test = 'update the Experience';

        }else if($workerExperienceData && ($level_of_experience == $workerExperienceData->experience_id)){
            //has experience data, but no changes
            $workerExperienceTransaction = true;
            $test = 'notUpdating the experience';

        }else{
            //no expereience data
            $companyExperienceAndBurdenData = $this->getCompanyExperienceAndBurdenDataUsingId($level_of_experience, $company_id);

            $newWorkerExperienceData = array(
                'worker_id' => $workerData->id,
                'experience_id' => $level_of_experience,
                'expertise_id' => $expertise,
                'is_worker' => 'true',
                'experience' => $payload->other_experience,
                'expertise' => $payload->other_expertise,
                'burden_rate_id' => $companyExperienceAndBurdenData->company_burden_rate_id,
            );
            
            $workerExperienceTransaction = $this->addWorkerExperience($newWorkerExperienceData);
            $test = 'crete new worke experience';
        }


        /*========================================================================================================
            worker areas
        ========================================================================================================*/
        // remove worker work areas if has one
        $this->removeWorkerWorkAreaList($workerData->id);

        if (is_array($areas_of_work) || is_object($areas_of_work)) {
            foreach ($areas_of_work as $area_of_work) {
                $worker_areas_of_work_data = array(
                    'work_area_id' => $area_of_work,
                    'worker_id' => $workerData->id,
                    'worker_type' => $worker_type,
                );
                $workerWorkAreaListTransaction = WorkAreaLists::create($worker_areas_of_work_data);
            }
        } else {
            return $areas_of_work;
        }


        /*========================================================================================================
        preparing worker availability data to be saved on database
        ========================================================================================================*/
        $min_availability_date = date("Y-m-d");
        $max_availability_date = date('Y-m-d', strtotime('+10 years'));
        $worker_availability_data = array(
            'worker_id' => $workerData->id,
            'company_id' => $company_id,
            'worker_type' => 'inhouse',
            'rate' => $workerData->rate,
            'min_availability_date' => $min_availability_date,
            'max_availability_date' => $max_availability_date,
        );
        $workerAvailabilityTransaction = $this->saveWorkerAvailability($worker_availability_data);

        //CREATE WORKER FILES
        $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' => $workerData->id,
                'name' => str_replace ( '.'.$resumeExtension, '', $name), //remove the extension '.pdf'
                'location' => $resumeFilePathName
            );
            WorkerFile::create($newCertificate);
        }

        /*========================================================================================================
        If there's an error or queries don't do their job, rollback!
        ========================================================================================================*/
        if (!$workerWorkAreaListTransaction && !$workerAvailabilityTransaction && !$workerExperienceTransaction) {
            DB::rollBack();
            return response()->json(
                ['error' => 'Server error.',
                    'company_work_area_list_result' => $workerWorkAreaListTransaction,
                    'workerAvailabilityTransaction' => $workerAvailabilityTransaction,
                    'workerExperienceTransaction' => $workerExperienceTransaction,
                    'test' => $test,
                ]);

        } else {
            event(new CreateSearchDataForInvitedWorkerEvent($workerData->id)); //Event for creating search data on newly accepted worker
            DB::commit();
            return response()->json(['success' => true, 'message' => 'Data updated successfully.' , 'test' => $test])->setStatusCode(201);
        }

    }

    public function isBurdenRateExisting($companyID, $experienceID){
        return CompanyBurdenRate::where('company_id', $companyID)
            ->where('company_experience_id', $experienceID)
            ->first();
    }

    public function createBurdenRate($data)
    {
        return CompanyBurdenRate::create($data);
    }

    public function updateBurdenRate($burdenRateID, $data){
        return CompanyBurdenRate::where('id', $burdenRateID)->update($data);
    }

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

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

    public function getCompanyBurdenRateData($experienceID){
        return CompanyBurdenRate::where('company_experience_id', $experienceID)->first();
    }

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

    public function addWorkerExperience($data){
        return WorkerExperience::create($data);
    }

    public function getCompanyExperience($experienceID, $companyID){
        return CompanyExperience::where('id', $experienceID)->where('company_id', $companyID)->first();
    }

    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();
    }
}
