<?php

namespace App\Modules\Company\Users;

use App\Models\Availabilities;
use App\Models\CompanyUser;
use App\Jobs\Company\ProcessSendEmailForNewCompanyUser;
use App\Traits\GoogleBucket;
use App\Traits\ZipcodeTraits;
use App\Traits\CheckEmployeeCountTraits;
use App\Traits\CreateUserActionTraits;
use App\Traits\GenerateAndValidateWorkerID;
use App\Traits\MobileNumberValidatorTraits;
use App\Traits\UserTraits;
use App\Traits\ValidatorTraits;
use App\Models\WorkAreaLists;
use App\Models\Worker;
use App\Models\Role;
use App\Models\WorkerFile;
use App\Models\WorkerExperience;
use App\Models\CompanyExperience;
use App\Models\CompanyBurdenRate;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use App\Events\Company\CreateSearchWorkerEvent;
use App\Modules\RemoteStorage\GoogleBucketGateway;
use Illuminate\Support\Facades\Validator;

class AddUser
{
    use CheckEmployeeCountTraits, CreateUserActionTraits, GenerateAndValidateWorkerID, 
        MobileNumberValidatorTraits, UserTraits, ValidatorTraits, ZipcodeTraits, GoogleBucket;

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

    /* add is also being used by App\Http\Controllers\API\Company\User\BatchInvitationLinkController */
    public function add($payload, $onError = NULL)
    {
        DB::beginTransaction();
        $companyId = isset($payload->company_id) ? $payload->company_id : $this->getCurrentUser()->company_id;
        $firstName = ucwords($payload->first_name);
        $lastName = ucwords($payload->last_name);
        $emailAddress = strtolower($payload->email_address);
        $phoneNum = '+1' . str_replace(array('-', '(', ')', ' '), '', $payload->phone_num);
        $profilePicture = $payload->profile_picture;
        $streetAddress = $payload->street_address;
        $city = $payload->city;
        $state = $payload->state;
        $zipcode = $payload->zipcode;
        $hourlyRate = $payload->hourly_rate;
        $dateHired = date('Y-m-d', strtotime($payload->date_hired));
        $additionalDetails = $payload->additional_details;
        $additionalDetails = $additionalDetails == 'null' || $additionalDetails == ''? '' : $additionalDetails;
        $isActive = 'true';
        $enable2fa = 'false';
        $isAccountDisabled = 'false';
        $willingDistance = $payload->willing_distance;
        $isInviteLink = $payload->isInviteLink ? true : false;

        // worker experiences data
        $expertise = $payload->expertise;
        $levelOfExperience = $payload->level_of_experience;
        $isWorker = false;
        $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);
        $fileNames = $payload->files_names;


        // check subscription
        if (in_array(5, $roles)) { // 5 = Worker
            $validate = $this->employeeCountValidation($companyId);
            if ($validate) {
                return $validate;
            }
        }

        // validate query param
        $rules = array(
            'first_name' => 'required|max:50',
            'last_name' => 'required|max:50',
            'email_address' => 'required|max:191',
            'phone_num' => 'required|max:50',
            'zipcode' => 'required|string|min:5|max:5',
            'hourly_rate' => 'required|numeric|min:1|max:10000',
            // 'street_address' => 'required|max:50',
            // 'city' => 'required|max:50',
            // 'state' => 'required|max:50',
            // 'date_hired' => 'required',
        );
        if (in_array(5, $roles)) { // 5 = Worker
            $isWorker = true;
            $rules['expertise'] = 'required';
            $rules['level_of_experience'] = 'required';
            $rules['willing_distance'] = 'required';
            $rules['areas_of_work'] = 'required';
            // $rules['files_names'] = 'required';
        }

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

        $validate = $this->validateEmailAddress($emailAddress, $companyId);
        if ($validate) {
            return $validate;
        }

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

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

        // Certificates file name validation, for added with worker role
        if ($fileNames) { 
            $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()){
                return [
                    'errors' => $validator->errors()->first()
                ];
            }
        }

        // generate new worker id
        $workerGeneratedId = $this->generateWorkerId($companyId);

        // validate worker id if already existed on specific company
        $validate = $this->validateWorkerGeneratedId($companyId, $workerGeneratedId);
        if (!$validate) {
            return response()->json(['errors' => ['The worker id has already been taken.']])->setStatusCode(201);
        }

        $hasProfile = $profilePicture == 'null' || $profilePicture == ''?  false : true;
        $profilePicture = $hasProfile? $payload->profile_picture : null;
        // profile picture
        if ($hasProfile) {
            $image_64 = $profilePicture;
            $extension = explode('/', explode(':', substr($image_64, 0, strpos($image_64, ';')))[1])[1]; // .jpg .png
            $image = explode(',', $image_64)[1];

            $filePath = $this->createUniqueImageNameWorker($companyId, $extension);
            $this->uploadBase64Image($image, $filePath, $this->googleBucket);
            $profilePicture = $filePath;
        }

        // preparing worker data to be saved on database
        $password = Hash::make($payload->password ?: Str::random(8));
        $emailVerificationCode = hash('sha256', $plainTextToken = Str::random(80));
        $worker_data = array(
            'worker_id' => $workerGeneratedId,
            'first_name' => $firstName,
            'last_name' => $lastName,
            'email_address' => $emailAddress,
            'password' => $password,
            'email_verification_code' => $emailVerificationCode,
            'is_active' => $isActive,
            'phone_num' => $phoneNum,
            'enable_2fa' => $enable2fa,
            'street_address' => $streetAddress,
            'city' => $city,
            'state' => $state,
            'zipcode' => $zipcode,
            // 'rate' => $hourlyRate,
            'flat_rate' => $hourlyRate,
            'date_hired' => $dateHired,
            'additional_details' => $additionalDetails,
            'is_account_disabled' => $isAccountDisabled,
            'distance' => $willingDistance,
        );

        if($hasProfile){
            $worker_data['profile_picture'] = $profilePicture; //add profile link
        }
        $workerTransaction = $this->saveWorkerData($worker_data);

        // if($isWorker){
        //     // if jobseeker is created - upload file on google bucket
        //     $resumeExtension = $payload->file('resume')->extension();
        //     $resumeFilePathName = $this->createUniqueResumeNameWorker($companyId, $resumeExtension);
        //     $workerTransaction->resume_file_name = $resumeFilePathName;
        //     $workerTransaction->save(); //save the resume fil path on database
        //     $filePayloadName = 'resume'; //input name of the file on the payload
        //     //upload resume file in the google bucket
        //     $this->uploadNewFile($payload, $resumeFilePathName, $filePayloadName, $this->googleBucket);
        // }

        $workerId = $workerTransaction->id;

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

        // preparing worker data additional role to be saved on database
        $companyUserRoleTransaction = 1;
        foreach ($roles as $role) {
            $companyUserRole = array(
                'worker_id' => $workerId,
                'company_id' => $companyId,
                'role_id' => $role,
                'availability_status' => $role == 5 ? 'reserved' : '',
            );
            $companyUserRoleTransaction = $this->saveCompanyUser($companyUserRole);
            if (!$companyUserRoleTransaction) {
                DB::rollBack();
                if (is_callable($onError)) {
                    call_user_func($onError);
                }
                return response()->json(['error' => 'Server error.']);
            }
        }

        if (in_array(5, $roles)) { // 5 = Worker
            // preparing worker experiences data to be saved on database
            $this->removeDuplicateBurdenRate($payload->level_of_experience); //to remove duplicate company burden rate on specific company experience bug - 
            
            //new added experience = new burden rate and new company experience
            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;
                } 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;
                }
            } 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;
            }
            
            $newWorkerExperienceData = array(
                'worker_id' => $workerId,
                'experience_id' => $levelOfExperience,
                'expertise_id' => $expertise,
                'is_worker' => 'true',
                'experience' => $payload->other_experience,
                'expertise' => $payload->other_expertise,
                'burden_rate_id' => $burdenRateId
            );
            
            $workerExperienceTransaction = $this->saveWorkerExperience($newWorkerExperienceData);

            // worker areas
            $workerAreasOfWorkTransaction = '';
            foreach ($areasOfWork as $areaOfWork) {
                $workerAreasOfWorkData = array(
                    'work_area_id' => $areaOfWork,
                    'worker_id' => $workerId,
                    'worker_type' => $workerType,
                );
                $workerAreasOfWorkTransaction = $this->saveWorkerWorkAreas($workerAreasOfWorkData);
                if (!$workerAreasOfWorkTransaction) {
                    DB::rollBack();
                    if (is_callable($onError)) {
                        call_user_func($onError);
                    }
                    return response()->json(['error' => 'Server error.']);
                }
            }
        }

        // 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' => $workerId,
            'company_id' => $companyId,
            'worker_type' => 'inhouse',
            'rate' => $hourlyRate,
            'min_availability_date' => $minAvailabilityDate,
            'max_availability_date' => $maxAvailabilityDate,
        );
        $companyWorkerAvailabilityTransaction = $this->saveWorkerAvailability($workerAvailabilityData);

        // added user transaction for reports
        $regRoles = $this->regRoles($roles);
        $roleNames = $regRoles->roleNames;
        $lastRole = $regRoles->lastRole;

        if(count($roles)>1){ 
            $actionTransaction = $isInviteLink ?: $this->createUserAction("Added " . $firstName . " " . $lastName . " as ". join(", ", $roleNames). ", and " .$lastRole);
        }else{
            $actionTransaction = $isInviteLink ?: $this->createUserAction("Added " . $firstName . " " . $lastName . " as ".$lastRole);
        }

        $isWorker = false; //reset the variable to false for triggering the event
        $url_part = $isInviteLink ? '/verify-email?code=' : '/setup-account?token=';
        // send email to worker/user for verifying the email address and setting up the password
        if (count($roles) == 1 && in_array(5, $roles)) { // 5 = Worker
            $isWorker = true; //used to triggered event
            $verificationUrl = rtrim(env('MOBILE_LINK'), '/') . $url_part . $emailVerificationCode;
        } else {
            $verificationUrl = rtrim(env('APP_FRONT_URL'), '/') . $url_part . $emailVerificationCode;
            // $verificationUrl = env('APP_URL') . $url_part . $emailVerificationCode;
        }
        $worker_data = [
            'first_name' => $firstName,
            'last_name' => $lastName,
            'email_address' => $emailAddress,
            'from-invite-link' => $isInviteLink,
            'verification_url' => $verificationUrl,
            'company_name' => $payload->company_name ?: $this->getCurrentUserCompanyName()->name,
            'role' => $roles,
        ];
        ProcessSendEmailForNewCompanyUser::dispatch($emailAddress, $worker_data)->onQueue('sendemail')->onConnection('jobs_emails');

        // if there's an error or queries don't do their job, rollback!
        $conditions = '';
        if ((in_array(5, $roles))) { // 5 = Worker
            $conditions = !$workerTransaction ||
            !$companyUserRoleTransaction ||
            !$workerExperienceTransaction ||
            !$workerAreasOfWorkTransaction ||
            !$companyWorkerAvailabilityTransaction ||
            !$actionTransaction;
        } else {
            $conditions = !$workerTransaction ||
            !$companyUserRoleTransaction ||
            !$actionTransaction;
        }

        if ($conditions) {
            DB::rollBack();
            if (is_callable($onError)) {
                call_user_func($onError);
            }
            return response()->json(['error' => 'Server error.', 
                'workerTransaction' => $workerTransaction,
                'companyUserRoleTransaction' => $companyUserRoleTransaction,
                'workerExperienceTransaction' => $workerExperienceTransaction,
                'workerAreasOfWorkTransaction' => $workerAreasOfWorkTransaction,
                'companyWorkerAvailabilityTransaction' => $companyWorkerAvailabilityTransaction,
                'actionTransaction' => $actionTransaction,
            ]);
        }  else {
            if ($isWorker) {
                event(new CreateSearchWorkerEvent($companyUserRoleTransaction)); //Event for creating search data for worker
            }
            DB::commit();
            return response()->json(['success' => 'Data added successfully.'])->setStatusCode(201);
        }
    }

    public function validateEmailAddress($emailAddress, $companyId, $field_name = 'email_address')
    {
        $companyWorkerData = Worker::select('company_users.company_id')
            ->leftJoin('company_users', 'company_users.worker_id', '=', 'workers.id')
            ->where('workers.email_address', $emailAddress)
            ->withTrashed()
            ->first();
        if ($companyWorkerData) {
            if ($companyId == $companyWorkerData->company_id) {
                return response()->json([
                    'errors' => ["Email address was already been used in your company."],
                    'field_name' => $field_name
                ])->setStatusCode(201);
            } else {
                return response()->json([
                    'errors' => ["Email address was already been used by another company."],
                    'field_name' => $field_name
                ])->setStatusCode(201);
            }
        }
        return false;
    }

    public function saveWorkerData($worker_data)
    {
        return Worker::create($worker_data);
    }

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

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

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

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

    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 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 regRoles($roles){
        $getRoles = Role::whereIn('id', $roles)->orderBy('id', 'asc')->get();
        $getRoles = json_decode($getRoles);

        $roleNames = array_map(function($obj){
            if($obj->id == 1){
                return "Admin";
            }
            return $obj->name;
        }, $getRoles);

        $vowels = "/[aeiou]/i";
        $roleNames[0] = preg_match($vowels, $roleNames[0][0])? "an ".$roleNames[0]: "a ".$roleNames[0];  
        $lastRole = array_pop($roleNames);

        return (object) array(
            'roleNames'=> $roleNames,
            'lastRole' => $lastRole
        );
    }
}
