<?php

namespace App\Http\Controllers\api;

use Exception;
use Carbon\Carbon;
use App\Traits\LoanTrait;
use App\Traits\PaymentTrait;
use App\Models\Application\Loan;
use App\Models\Application\Group;
use Illuminate\Support\Facades\DB;
use App\Http\Requests\LoanRequest;
use App\Models\Application\Status;
use App\Http\Controllers\Controller;
use App\Models\Application\LoanType;
use App\Models\Application\Guarantees;
use App\Models\Application\LoanEndorsement;
use Illuminate\Support\Facades\Log;

class LoanController extends Controller
{
    use LoanTrait, PaymentTrait;

    protected $MESSAGES = [
        'INDEX' => 'WARNING.LOAN_INDEX',
        'STORE' => 'WARNING.LOAN_STORE',
        'DESTROY' => 'WARNING.LOAN_DESTROY',
        'VISIBLE' => 'WARNING.LOAN_VISIBLE',
        'SHOW' => 'WARNING.LOAN_SHOW',
        'RENEW' => 'WARNING.LOAN_RENEW',
        'REESTRUCTURE' => 'WARNING.LOAN_REESTRUCTURE',
    ];

    public function __construct()
    {
        $this->CHANNEL = 'loan_errors';
    }

    public function index(LoanRequest $request)
    {
        try {
            $isSystemAdministrator = $this->hasSystemAdministrator($request->fk_office);
            $isAdministrator = $this->hasAppOwner($request->fk_office) || $this->hasSiteAdministrator($request->fk_office) || $this->hasSystemAdministrator($request->fk_office);

            $loans = Loan::with([
                'loan_type',
                'group',
                'promoter',
                'client',
                'executive',
                'supervisor',
                'collaborator',
                'status',
                'guarantees',
                'next_payment',
                'details',
                'user_updater',
                'user_creator'
            ])->withCount([
                'payments as current_payment' => function ($query) {
                    $query->whereHas('status', function ($status) {
                        $status->whereNotIn('statusname', ['FALLA', 'INCOMPLETO', 'PENDIENTE']);
                    })->where('is_active', true)
                        ->whereColumn('payment', '>=', 'weeklypayment');
                }
            ])->withCount(['payments as duration'])->where([
                ['fk_site', $request->fk_site],
                ['fk_office', $request->fk_office],
                ['is_active', !$this->hasSpecial($request->fk_office)]
            ])->whereHas('status', function ($query) {
                $query->whereNotIn('statusname', $this->waitLoans);
            })->when(!$isSystemAdministrator, function ($query) {
                $query->where('visible', true);
            })->when(!$isAdministrator, function ($query) {
                $query->whereIn('fk_group', $this->getUserGroups());
            })->orderBy('loandate', 'DESC')->orderBy('id', 'DESC')->get();

            return $this->responseJson(null, $loans, 201);
        } catch (Exception $ex) {
            $this->generateLog($this->CHANNEL, $ex);
            return $this->responseJson($this->MESSAGES['INDEX'], null, 501);
        }
    }

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

        try {
            $loanType = LoanType::findOrFail($request->fk_loan_type);

            $loanDate = Carbon::createFromDate($request->loandate);
            $allowedDay = strtoupper($loanType->alloweddays);

            if (strtoupper($loanDate->format('l')) !== $allowedDay && $allowedDay != 'ALL') {
                $loanDate = $loanDate->previous($this->convertToCarbonDay($allowedDay));
            }

            $loanDate = $loanDate->format('Y-m-d');

            $loans = json_decode(json_encode($request->loans));

            foreach ($loans as $loan) {
                $wasProcessed = true;

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

                if (!$this->AllowMultiLoans($request)) {
                    $wasProcessed = $this->renewLoan($loan);
                }

                $group = Group::findOrfail($request->fk_group);

                $employee = $this->hasEmployee($loan);
                $isEmployee = !empty($employee?->has_employee) && $employee->has_employee;

                $loanItem = Loan::create([
                    'fk_site' => $request->fk_site,
                    'fk_office' => $request->fk_office,
                    'fk_loan_type' => $request->fk_loan_type,
                    'fk_group' => $group->id,
                    'fk_promoter' => $request->fk_promoter != $group->fk_promoter ? $request->fk_promoter : ($request->fk_promoter ?? $group->fk_promoter),
                    'fk_client' => $loan->fk_client,
                    'loanamount' => $loan->loanamount,
                    'weeklypayment' => $loan->weeklypayment,
                    'duration' => ($loanType->duration ?? $request->duration) - ($isEmployee && !$loanType->hasspecial ? 2 : 0),
                    'loandate' => $loanDate ?? $request->loandate,
                    'loanenddate' => $isEmployee ? Carbon::createFromDate($request->loanenddate)->subWeeks(2)->format('Y-m-d') : $request->loanenddate,
                    'realamount' => $loan->realamount,
                    'fk_executive' => $request->fk_executive,
                    'hasspecial' => $isEmployee || $loanType->hasspecial,
                    'fk_supervisor' => $request->fk_supervisor,
                    'fk_collaborator' => $isEmployee ? $employee->id : null,
                    'fk_usercreator' => $this->user()->id
                ]);

                $this->createPayments($loanItem, $loan->firstpay, $isEmployee);
                $this->addLoanDetails($loanItem, $loan);

                if (isset($lastLoan) && $wasProcessed && !$this->AllowMultiLoans($request)) {
                    $lastLoan->fk_loan_renewed = $loanItem->id;
                    $lastLoan->fk_userupdater = $this->user()->id;

                    $lastLoan->update();
                }

                if ($loan->endorsements && count($loan->endorsements) > 0) {
                    $this->addEndorsments($loanItem, $loan);
                }

                if ($loan->guarantees && count($loan->guarantees)) {
                    $this->addGuarantees($loanItem, $loan);
                }

                $this->determineClientStatus($loanItem);
                $this->determineEndorsementStatus($loanItem);
            }

            return $this->responseJson(null, true, 201);
        } catch (Exception $ex) {
            $this->generateLog($this->CHANNEL, $ex);
            return $this->responseJson($this->MESSAGES['STORE'], null, 201);
        }
    }

    public function destroy(LoanRequest $request)
    {
        try {
            $loan = Loan::findOrFail($request->id);

            $loan->update([
                'is_active' => $this->hasSpecial($request->fk_office),
                'fk_userupdater' => $this->user()->id
            ]);

            return $this->responseJson(null, true, 201);
        } catch (Exception $ex) {
            $this->generateLog($this->CHANNEL, $ex);
            return $this->responseJson($this->MESSAGES['DESTROY'], null, 201);
        }
    }

    public function show(LoanRequest $request)
    {
        try {
            $loan = Loan::select(
                'id',
                'fk_loan_type',
                'fk_group',
                'duration',
                'loandate',
                'loanenddate',
                'loanamount',
                'weeklypayment',
                'realamount',
                'fk_client',
                'fk_executive',
                'fk_supervisor',
                'fk_promoter',
                'fk_status',
            )->with([
                'loan_type' => fn($loanType) => $loanType->select(
                    'id',
                    'loantypename',
                    'period'
                ),
                'status' => fn($status) => $status->select(
                    'id',
                    'statusname',
                    'classname'
                ),
                'group',
                'details' => fn($details) => $details->select(
                    'fk_loan',
                    'renewpayment',
                    'paperworkpayment',
                    'otherpayment',
                    'interesrate'
                )->where('is_active', true),
                'payments' => fn($payments) => $payments->with(['status'])->select(
                    'id',
                    'fk_loan',
                    'fk_status',
                    'paymentnumber',
                    'duedate',
                    'payment',
                    'paymentdate'
                )->where('is_active', true),
                'client' => fn($client) => $client->where('is_active', true),
                'endorsements' => fn($endorsements) => $endorsements->select(
                    'id',
                    'fk_endorsement',
                    'fk_loan'
                )->with(['endorsement' => fn($endorsement) => $endorsement->with(['city'])->select(
                    'id',
                    'fk_city',
                    DB::raw("CONCAT(firstname, ' ', lastname, ' ', secondlastname) as fullname"),
                    'colony',
                    'address',
                    'phonenumber',
                )])->where('is_active', true),
                'executive',
                'supervisor',
                'promoter',
                'guarantees' => fn($guarantees) => $guarantees->where('is_active', true),
            ])->withCount([
                'payments as current_payment' => function ($query) {
                    $query->whereHas('status', function ($status) {
                        $status->whereNotIn('statusname', ['FALLA', 'INCOMPLETO', 'PENDIENTE']);
                    })->where('is_active', true)
                        ->whereColumn('payment', '>=', 'weeklypayment');
                }
            ])->findORFail($request->id);

            $loan->loan_debt = $this->getRealDebt($loan);
            $loan->has_commissions = !$loan->commissions->isNotEmpty() && !$loan->fk_collaborator;

            return $this->responseJson(null, $loan, 201);
        } catch (Exception $ex) {
            $this->generateLog($this->CHANNEL, $ex);
            return $this->responseJson($this->MESSAGES['SHOW'], null, 501);
        }
    }

    public function showRenew(LoanRequest $request)
    {
        try {
            $loan = Loan::with(['client', 'endorsements', 'loan_type', 'commissions'])
                ->findOrFail($request->id);

            $loan->loanamount += 500;
            $loan->loan_debt = $this->getRealDebt($loan);

            return $this->responseJson(null, $loan->only(['id', 'duration', 'loandate', 'loanamount', 'loan_debt', 'client', 'endorsements', 'loan_type']), 201);
        } catch (Exception $ex) {
            $this->generateLog($this->CHANNEL, $ex);
            return $this->responseJson($this->MESSAGES['SHOW'], null, 501);
        }
    }

    public function renew(LoanRequest $request)
    {
        try {
            $loan = Loan::findOrFail($request->id);
            $loanType = (clone $loan)->loan_type;

            $this->renewUniqueLoan($loan);

            $group = Group::findOrFail($loan->fk_group);

            $employee = $this->hasEmployee($loan);
            $isEmployee = !empty($employee?->has_employee) && $employee->has_employee === true;

            $loanItem = Loan::create([
                'fk_site' => $request->fk_site,
                'fk_office' => $request->fk_office,
                'fk_loan_type' => $request->fk_loan_type,
                'fk_group' => $loan->fk_group,
                'fk_promoter' => $group->fk_promoter,
                'fk_client' => $loan->fk_client,
                'loanamount' => $request->loanamount,
                'weeklypayment' => $request->weeklypayment,
                'duration' => $request->duration - ($isEmployee && !$loanType->hasspecial ? 2 : 0),
                'loandate' => $loanDate ?? $request->loandate,
                'loanenddate' => $isEmployee && !$loanType->hasspecial ? Carbon::createFromDate($request->loanenddate)->subWeeks(2)->format('Y-m-d') : $request->loanenddate,
                'realamount' => $request->realamount,
                'fk_executive' => $loan->fk_executive,
                'hasspecial' => $isEmployee || $loanType->hasspecial,
                'fk_supervisor' => $loan->fk_supervisor,
                'fk_collaborator' => $isEmployee ? $employee->id : null,
                'fk_usercreator' => $this->user()->id
            ]);

            $loan->fk_loan_renewed = $loanItem->id;
            $loan->fk_userupdater = $this->user()->id;
            $loan->update();

            $this->createPayments($loanItem, $loan->firstpay, $isEmployee);
            $this->addLoanDetails($loanItem, $request);

            if ($loan->endorsements && count($loan->endorsements) > 0) {
                $this->addEndorsments($loanItem, $loan);
            }

            if ($loan->guarantees && count($loan->guarantees)) {
                $this->addGuarantees($loanItem, $loan);
            }

            $this->determineClientStatus($loanItem);
            $this->determineEndorsementStatus($loanItem);

            return $this->responseJson(null, true, 201);
        } catch (Exception $ex) {
            $this->generateLog($this->CHANNEL, $ex);
            return $this->responseJson($this->MESSAGES['RENEW'], null, 501);
        }
    }

    public function reestructure(LoanRequest $request)
    {
        try {
            $currentLoan = Loan::findOrFail($request->id);
            $loanType = (clone $currentLoan)->loan_type;
            $loanReestructureStatus = Status::where('statusname', 'SOLICITUD REESTRUCTURADA')->first();
            $paymentReestructureStatus = Status::where('statusname', 'REESTRUCTURADO')->first();

            DB::beginTransaction();

            foreach ($currentLoan->payments as $payment) {
                if (!$payment->payment || $payment->payment < $currentLoan->weeklypayment) {
                    $payment->update([
                        'payment' => $currentLoan->weeklypayment,
                        'paymentdate' => Carbon::now()->format('Y-m-d'),
                        'fk_status' => $paymentReestructureStatus->id,
                        'fk_userupdater' => $this->user()->id
                    ]);
                }
            }

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

            $employee = $this->hasEmployee($currentLoan);
            $isEmployee = !empty($employee?->has_employee) && $employee->has_employee === true;

            $loan = Loan::create([
                'fk_site' => $currentLoan->fk_site,
                'fk_office' => $currentLoan->fk_office,
                'fk_loan_type' => $request->fk_loan_type,
                'fk_group' => $currentLoan->fk_group,
                'fk_promoter' => $currentLoan->fk_promoter,
                'fk_client' => $currentLoan->fk_client,
                'loanamount' => $request->loanamount,
                'weeklypayment' => $request->weeklypayment,
                'duration' => $request->duration - ($isEmployee && !$loanType->hasspecial ? 2 : 0),
                'loandate' => $request->loandate,
                'loanenddate' => $isEmployee && !$loanType->hasspecial ? Carbon::createFromDate($request->loanenddate)->subWeeks(2)->format('Y-m-d') : $request->loanenddate,
                'realamount' => $request->realamount,
                'fk_executive' => $currentLoan->fk_executive,
                'hasspecial' => $isEmployee || $loanType->hasspecial,
                'fk_supervisor' => $currentLoan->fk_supervisor,
                'fk_collaborator' => $isEmployee ? $employee->id : null,
                'fk_usercreator' => $this->user()->id
            ]);

            $this->createPayments($loan, $request->firstpay, $isEmployee);
            $this->addLoanDetails($loan, $request);

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

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

            $this->determineClientStatus($loan);
            $this->determineEndorsementStatus($loan);

            DB::commit();

            return $this->responseJson(null, true, 201);
        } catch (Exception $ex) {
            DB::rollBack();
            $this->generateLog($this->CHANNEL, $ex);
            return $this->responseJson($this->MESSAGES['REESTRUCTURE'], null, 501);
        }
    }
}
