<?php

namespace App\Traits;

use Exception;
use Carbon\Carbon;
use App\Models\Application\Loan;
use Illuminate\Support\Facades\DB;
use App\Models\Application\Status;
use App\Models\Application\Client;
use App\Models\Bussiness\Collaborator;
use App\Models\Application\LoanDetail;
use App\Models\Application\LoanEndorsement;
use App\Models\Administration\Configuration;
use App\Models\Application\Guarantees;

trait LoanTrait
{
    use UserLoggedTrait;

    protected $waitLoans = [
        'SOLICITUD EN APROBACIÓN',
        'SOLICITUD APROBADA',
        'SOLICITUD RECHAZADA'
    ];

    /**
     * Convert a day string to a Carbon day constant.
     *
     * @param string $day The day string (e.g., 'MONDAY', 'TUESDAY').
     * @return int The Carbon day constant.
     */
    protected function convertToCarbonDay($day)
    {
        $days = [
            'MONDAY' => Carbon::MONDAY,
            'TUESDAY' => Carbon::TUESDAY,
            'WEDNESDAY' => Carbon::WEDNESDAY,
            'THURSDAY' => Carbon::THURSDAY,
            'FRIDAY' => Carbon::FRIDAY,
            'SATURDAY' => Carbon::SATURDAY,
            'SUNDAY=>' => Carbon::SUNDAY
        ];

        return $days[$day];
    }

    /**
     * Validate if a given date corresponds to a specific day of the week.
     *
     * @param string $date The date to be validated (format: 'Y-m-d').
     * @param string $day The day of the week (e.g., 'MONDAY', 'TUESDAY', 'ALL').
     * @return bool True if the date corresponds to the specified day, false otherwise.
     */
    protected function isDateOnDay($date, $day)
    {
        if ($day === 'ALL') {
            return true;
        }

        $carbonDate = Carbon::parse($date);
        $carbonDay = $this->convertToCarbonDay($day);

        return $carbonDate->dayOfWeek === $carbonDay;
    }

    /**
     * Check if a client has an associated employee.
     *
     * @param Any $request The loan any object.
     * @return Collaborator|null The associated collaborator or null if not found.
     */
    protected function hasEmployee($request)
    {
        $client = Client::findOrFail($request->fk_client);

        $collaborator = Collaborator::where([
            ['firstname', $client->firstname],
            ['lastname', $client->lastname],
            ['secondlastname', $client->secondlastname],
        ])->first() ?? null;

        if ($collaborator) {
            $collaborator->has_employee = $client->has_employee;
        }

        return $collaborator;
    }

    /**
     * Determine if multiple loans are allowed for a client.
     *
     * @param mixed $data The data containing site and client information.
     * @return bool True if multiple loans are allowed, false otherwise.
     */
    protected function AllowMultiLoans($data)
    {
        try {
            $configuration = Configuration::where([
                ['fk_site', $data->fk_site],
                ['name', "ALLOW_MULTI_LOANS"]
            ])->first();

            return $configuration->value == 'TRUE';
        } catch (Exception $ex) {
            return false;
        }
    }

    /**
     * Adds endorsements to a loan.
     *
     * This method takes a loan and an array of endorsements, and attempts to add each endorsement
     * to the loan. It begins a database transaction, iterates through the endorsements, and for each
     * endorsement, it checks if the endorsement exists in the Client model. If the endorsement exists,
     * it creates a new LoanEndorsement record linking the loan and the endorsement.
     * 
     * If any exception occurs during the process, the transaction is rolled back and the method returns false.
     * If the process completes successfully, the transaction is committed and the method returns true.
     *
     * @param Loan $loan The loan to which endorsements will be added.
     * @param array $endorsements An array of endorsements to be added to the loan.
     * @return bool Returns true if endorsements are successfully added, false otherwise.
     */
    protected function addEndorsments(Loan $loan, $data)
    {
        try {
            $endorsements = $data->endorsements;

            if ($endorsements && count($endorsements) > 0) {
                DB::beginTransaction();

                foreach ($endorsements as $endorsement) {
                    $endorsement = Client::find($endorsement->id);

                    if (!$endorsement) {
                        continue;
                    }

                    LoanEndorsement::create([
                        'fk_loan' => $loan->id,
                        'fk_endorsement' => $endorsement->id,
                        'fk_usercreator' => $this->user()->id
                    ]);
                }

                DB::commit();
            }
            return true;
        } catch (Exception $ex) {
            DB::rollback();
            return false;
        }
    }

    /**
     * Adds guarantees to a loan.
     *
     * This method takes a Loan object and an array of guarantees, and creates
     * GuaranteeModel entries for each guarantee in the array. It wraps the
     * operation in a database transaction to ensure atomicity.
     *
     * @param Loan $loan The loan object to which guarantees will be added.
     * @param array $guarantees An array of guarantees to be added to the loan.
     * 
     * @return bool Returns false if an exception occurs during the transaction.
     */

    protected function addGuarantees(Loan $loan, $data)
    {
        try {
            DB::beginTransaction();
            $guarantees = $data->guarantees;

            foreach ($guarantees as $guarantee) {
                $guarantee = Guarantees::create([
                    'fk_loan' => $loan->id,
                    'fk_client' => $guarantee->client->id,
                    'fk_model' => $guarantee->model->id ?? null,
                    'description' => $guarantee->description,
                    'serialnumber' => $guarantee->serialnumber,
                    'fk_usercreator' => $this->user()->id
                ]);
            }

            DB::commit();
            return true;
        } catch (Exception $ex) {
            DB::rollBack();
            return false;
        }
    }

    /**
     * Renew a client's loan.
     *
     * @param $request The loan request object.
     * @return bool True if the loan was successfully renewed, false otherwise.
     */
    protected function renewLoan($request)
    {
        try {

            $loans_status_ids = Status::whereNotIn('statusname', [
                'SOLICITUD EN APROBACIÓN',
                'SOLICITUD APROBADA',
                'SOLICITUD RECHAZADA',
                'SOLICITUD RENOVADA',
                'SOLICITUD REESTRUCTURADA',
                'SOLICITUD FINALIZADA',
            ])->pluck('id');

            $lastLoan = Loan::where([
                ['fk_client', $request->fk_client],
                ['is_active', true]
            ])->whereIn('fk_status', $loans_status_ids)
                ->orderBy('id')->first();

            $this->renewUniqueLoan($lastLoan);

            return true;
        } catch (Exception $ex) {
            DB::rollBack();
            return false;
        }
    }

    /**
     * Renews the status of the last loan and updates its payments.
     *
     * This method performs the following actions:
     * 1. Begins a database transaction.
     * 2. Updates the status of the last loan to 'SOLICITUD RENOVADA'.
     * 3. Loads the payments of the last loan that have statuses 'PENDIENTE', 'FALLA', or 'INCOMPLETO'.
     * 4. Updates each payment's amount, date, status to 'ADELANTADO POR RENOVACION', and user updater.
     * 5. Commits the database transaction.
     *
     * @param \App\Models\Loan $lastLoan The last loan to be renewed.
     * @return void
     */
    protected function renewUniqueLoan($lastLoan, $flag = false)
    {
        if ($lastLoan) {
            DB::beginTransaction();
            $renewStatus = Status::where('statusname', 'SOLICITUD RENOVADA')->value('id');

            $lastLoan->update([
                'fk_status' => $renewStatus,
                'fk_userupdater' => $this->user()->id
            ]);

            $lastLoan->load(['payments' => function ($query) {
                $query->whereHas('status', function ($query) {
                    $query->whereIn('statusname', ['PENDIENTE', 'FALLA', 'INCOMPLETO']);
                });
            }]);

            $renewPaymentStatus = Status::where('statusname', 'ADELANTADO POR RENOVACION')->value('id');
            $onTimePaymentStatus = Status::where('statusname', 'PUNTUAL')->value('id');

            foreach ($lastLoan->payments as $payment) {
                $payment->update([
                    'payment' => $lastLoan->weeklypayment,
                    'paymentdate' => $flag ? Carbon::createFromTimestamp($payment->duedate)->format('Y-m-d') : Carbon::now()->format('Y-m-d'),
                    'fk_status' => $flag ? $onTimePaymentStatus : $renewPaymentStatus,
                    'fk_userupdater' => $this->user()->id
                ]);

                $flag = false;
            }

            DB::commit();
        }
    }

    /**
     * Adds loan details to the LoanDetail model.
     *
     * This method creates a new LoanDetail record using the provided loan object.
     * It captures various payment details and the interest rate associated with the loan.
     * If an exception occurs during the creation process, the method returns false.
     *
     * @param object $loan The loan object containing details to be added.
     * @return bool Returns false if an exception occurs, otherwise void.
     */
    protected function addLoanDetails(Loan $loan, $data)
    {
        try {
            LoanDetail::create([
                'fk_loan' => $loan->id,
                'renewpayment' => $data->renewpayment,
                'paperworkpayment' => $data->paperworkpayment,
                'otherpayment' => $data->otherpayment,
                'interesrate' => $data->interesrate,
                'fk_usercreator' => $this->user()->id,
            ]);
        } catch (Exception $ex) {
            return false;
        }
    }

    /**
     * Calculate the real debt of a loan by considering the payments made and their statuses.
     *
     * @param Loan $loan The loan object for which the real debt is to be calculated.
     * @return float The calculated real debt.
     */
    protected function getRealDebt(Loan $loan)
    {
        try {
            $debt = 0;

            $loan = Loan::findOrFail($loan->id)->load(['payments'
            => fn($payments) => $payments->with('status')->where('is_active', true)->whereHas('status', fn($status)
            => $status->whereNotIn('statusname', ['PUNTUAL', 'ADELANTADO', 'ADELANTO POR RENOVACION']))]);

            foreach ($loan->payments as $payment) {
                switch ($payment->status->statusname) {
                    case 'RECUPERADO':
                        if ($payment->payment == $loan->weeklypayment) {
                            break;
                        }
                    case 'INCOMPLETO':
                        if ($payment->payment != $loan->weeklypayment) {
                            $debt += ($loan->weeklypayment - $payment->payment);
                        }
                        break;
                    default:
                        $debt += $loan->weeklypayment;
                }
            }


            if ($loan->loan_type->period == 30 && !in_array($loan->status->statusname, ['SOLICITUD FINALIZADA', 'SOLICITUD RENOVADA', 'SOLICITUD REESTRUCTURADA'])) {
                $debt += $loan->loanamount;
            }
            return $debt;
        } catch (Exception $ex) {
        }
    }

    /**
     * Adjust the current date to the nearest allowed day.
     *
     * @param string $allowedDay The allowed day of the week ('ALL' for any day).
     * @return Carbon The adjusted date.
     */
    protected function adjustCurrentDate($allowedDay)
    {
        $currentDate = Carbon::now();
        $allowedDayCarbon = $this->convertToCarbonDay($allowedDay);

        if ($allowedDay !== 'ALL') {
            if ($currentDate->isDayOfWeek($allowedDay)) {
                return $currentDate;
            } else {
                $previousValidDate = (clone $currentDate)->previous($allowedDayCarbon)->addDay(1);

                $diffInDays = $currentDate->diffInDays($previousValidDate);

                if ($diffInDays == 7) {
                    return $currentDate;
                } elseif ($diffInDays > 0 && $diffInDays <= 4) {
                    return $currentDate->previous($allowedDay);
                } else {
                    return $currentDate->next($allowedDay);
                }
            }
        }

        return $currentDate;
    }
}
