<?php

namespace App\Services;

use App\Http\Resources\ProofPaymentResource;
use App\Mail\ProofPaymentMail;
use App\Models\AdminCompany;
use App\Models\Bank;
use App\Models\Company;
use App\Models\DetailUserCompany;
use App\Models\ProofPayment;
use App\Models\Salesman;
use App\Models\Supplier;
use App\Models\Ticket;
use App\Models\User;
use App\Models\WayToPayForCompany;
use App\Traits\HasResponse;
use Barryvdh\DomPDF\Facade\Pdf;
use Carbon\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use NumberToWords\NumberToWords;

class ProofPaymentService
{
    use HasResponse;

    public function list($withPagination)
    {
        $proofPayment = ProofPayment::proofPaymentFilters()->orderBy('id', 'desc');

        $proofPayment = !empty($withPagination)
            ? $proofPayment->paginate($withPagination['perPage'], page: $withPagination['page'])
            : $proofPayment->get();

        $proofPayment = ProofPaymentResource::collection($proofPayment->load('company', 'userCreated', 'acceptedUser', 'wayToPayForCompany'));

        return $this->successResponse('Lectura exitosa.', $proofPayment);
    }

    public function acceptDocument($id)
    {
        DB::beginTransaction();
        try {
            #Validar comprobante de pago
            $proofPayment = ProofPayment::activeForID($id)->company()->first();
            if (!$proofPayment) return $this->errorResponse('Comprobante de pago no válido.', 400);
            if ($proofPayment->payment_method != 1) return $this->errorResponse('El comprobante de pago seleccionado no necesita una verificación.', 400);

            $proofPayment->update(['accepted_iduser' => Auth::user()->id]);

            DB::commit();
            return $this->successResponse('Comprobante de pago verificado con éxito.', $proofPayment);
        } catch (\Throwable $th) {
            DB::rollBack();
            return $this->externalError('durante la verificación de un comprobante de pago.', $th->getMessage());
        }
    }

    public function delete($id)
    {
        DB::beginTransaction();
        try {
            #Validar comprobante de pago
            $proofPayment = ProofPayment::activeForID($id)->company()->first();
            if (!$proofPayment) return $this->errorResponse('Comprobante de pago no válido.', 400);

            $proofPayment->update(['status' => 2]);

            DB::commit();
            return $this->successResponse('Comprobante de pago eliminado con éxito.', $proofPayment);
        } catch (\Throwable $th) {
            DB::rollBack();
            return $this->externalError('durante la eliminado de un comprobante de pago.', $th->getMessage());
        }
    }

    # PDF
    public function getPDF($params)
    {
        try {
            # Verificar llaves y estructura inicial
            $validate = $this->checkFKandInitialStructure($params);
            if (!$validate->original['status']) return $validate;
            $dataInitial = $validate->original['data']['detail'];
            $amountText = $dataInitial['amount_text'];
            $expiration = $dataInitial['expiration'];
            $unpaidDocs = $dataInitial['unpaid_documents'];

            // Verificar tickets que hayan sido emitidos
            $validate = $this->checkTickets($params);
            if (!$validate->original['status']) return $validate;
            $dataTickets = $validate->original['data']['detail'];

            # Generamos el registro
            $proofPayment = [
                'id'                    => 1,
                'idcompany'             => request('idcompany'),
                'payment_method'        => $dataInitial['payment_method'],
                'idway_to_pay_for_company' => $params['idway_to_pay_for_company'],
                'idstickets'            => $params['idstickets'],
                'iduser_created'        => Auth::user()->id,
                'date_admission'        => $params['date_admission'] ?? null,
                'document_date'         => $params['document_date'] ?? null,
                'idbank'                => $params['idbank'] ?? null,
                'serie'                 => $params['serie'] ?? null,
                'amount'                => $params['amount'] ?? null,
                'amount_text'           => $amountText,
                'unpaid_documents'      => $unpaidDocs,
                'date_creation'         => now(),
                'authorized_quota'      => null,
                'used_quota'            => null,
                'available_space'       => null,
                'expiration'            => $expiration,
                'accepted_iduser'       => null
            ];

            # Verificar validez de la data
            $validate = $this->structureData($params,  $proofPayment);
            if (!$validate->original['status']) return $validate;
            $data = $validate->original['data']['detail'];

            // Si es de crédito solo muestro una preview del registro
            if ($dataInitial['payment_method'] == 2) {
                return $this->successResponse('El registro generado sería el siguiente.', $proofPayment);
            }
            $date = str_replace('-', '_', date('Y-m-d_H:i:s'));

            # AVANZAR CON PARTE DEL PDF
            if (!empty($data)) {
                $pdf = PDF::loadView('pdfs.bill', compact('data'));

                return $pdf->download('Factura' . $date . '.pdf');
            }

            return $this->errorResponse('Data no disponible.', 400);
        } catch (\Throwable $th) {
            return $this->externalError('durante la visualización del pdf.', $th->getMessage());
        }
    }

    public function savePDF($params)
    {
        DB::beginTransaction();
        try {

            # Verificar llaves y estructura inicial
            $validate = $this->checkFKandInitialStructure($params);
            if (!$validate->original['status']) return $validate;
            $dataInitial = $validate->original['data']['detail'];
            $amountText = $dataInitial['amount_text'];
            $expiration = $dataInitial['expiration'];
            $unpaidDocs = $dataInitial['unpaid_documents'];

            // Verificar tickets que hayan sido emitidos
            $validate = $this->checkTickets($params);
            if (!$validate->original['status']) return $validate;
            $dataTickets = $validate->original['data']['detail'];

            # Generamos el registro
            $proofPayment = ProofPayment::create([
                'idcompany'             => request('idcompany'),
                'payment_method'        => $dataInitial['payment_method'],
                'idway_to_pay_for_company' => $params['idway_to_pay_for_company'],
                'idstickets'            => $params['idstickets'],
                'iduser_created'        => Auth::user()->id,
                'date_admission'        => $params['date_admission'] ?? null,
                'document_date'         => $params['document_date'] ?? null,
                'idbank'                => $params['idbank'] ?? null,
                'serie'                 => $params['serie'] ?? null,
                'amount'                => $params['amount'] ?? null,
                'amount_text'           => $amountText,
                'unpaid_documents'      => $unpaidDocs,
                'date_creation'         => now(),
                'authorized_quota'      => null,
                'used_quota'            => null,
                'available_space'       => null,
                'expiration'            => $expiration,
                'accepted_iduser'       => null
            ]);

            # Verificar validez de la data
            $validate = $this->structureData($params, $proofPayment);
            if (!$validate->original['status']) return $validate;
            $data = $validate->original['data']['detail'];
            $pdfGenerate = false; // Si es de crédito solo muestro una creación del registro

            if (!empty($data) && $dataInitial['payment_method'] == 1) { // Si es de crédito solo muestro una preview del registro
                $pdf = Pdf::loadView('pdfs.bill', compact('data'));
                $rut_company = Company::find(request('idcompany'))->rut;
                $date = str_replace(['-', ':'], ['_', '_'], date('Y-m-d_H:i:s'));
                $rutUser = Auth::user()->rut;

                $pdfGenerate = true;
                $filePath = "public/pdf/$rut_company/bill/$rutUser/factura_$date.pdf";
                $proofPayment->update(['document_url' => $filePath]);

                if (Storage::exists($filePath)) {
                    # Si existe, eliminarlo antes de guardar el nuevo PDF
                    Storage::delete($filePath);
                }

                #Guardamos el pdf
                Storage::put($filePath, $pdf->output());
            } elseif (empty($data)) {
                return $this->errorResponse('Data no disponible.', 400);
            }

            // Guardar los montos parciales a pagar por ticket y marcarlo como emitido por comprobante
            foreach ($params['idstickets'] as $key => $ticket) {
                $ticket = Ticket::find($ticket);
                $proof_payment_amount = 0;
                if ($ticket->issued_proof_payment == 1) {
                    $proof_payment_amount = $ticket->proof_payment_amount + $params['payment_ticket'][$key];
                } else {
                    $proof_payment_amount = $params['payment_ticket'][$key];
                }

                $ticket->update([
                    'issued_proof_payment'  => 1,
                    'proof_payment_amount'  => $proof_payment_amount
                ]);
            }

            DB::commit();
            if ($pdfGenerate) return $this->successResponse('Generación de documento exitosa.', Storage::url($filePath));
            return $this->successResponse('El registro se ha geenrado con éxito.', $proofPayment);
        } catch (\Throwable $th) {
            DB::rollBack();
            return $this->externalError('durante el guardado del pdf.', $th->getMessage());
        }
    }

    private function structureData($params, $proofPayment)
    {
        // Data
        $user = Auth::user();
        $detailUser = DetailUserCompany::where('iduser', $user->id)->company()->active()->first();
        $dataUser = $this->dataUser($user->rut, $detailUser->iduser_type);

        // Data del usuario que aceptó el comprobante
        if (!is_null($proofPayment['accepted_iduser'])) {
            $reconciledByUser = User::find($params['accepted_iduser']);;
            $reconciledByUserDetail = DetailUserCompany::where('iduser', $reconciledByUser->id)->company()->active()->first();
            $reconciledByUserData = $this->dataUser($reconciledByUser->rut, $reconciledByUserDetail->iduser_type);
        }

        $company = Company::find(request('idcompany'));
        $userClient = User::find($params['idclient']);
        $detailUserClient = DetailUserCompany::where('iduser', $userClient->id)->company()->active()->first();
        $dataUserClient = $this->dataUser($userClient->rut, $detailUserClient->iduser_type);

        // Obtener el nombre del método de pago seleccionado
        $way_to_pay = WayToPayForCompany::find($proofPayment['idway_to_pay_for_company']);
        $way_to_pay = $way_to_pay->wayToPay->name;

        $dateTime = Carbon::parse($proofPayment['date_creation']);

        // Verificar tickets que hayan sido emitidos
        $validate = $this->checkTickets($params);
        if (!$validate->original['status']) return $validate;
        $checkTickets = $validate->original['data']['detail'];

        $validate = $this->dataTickets($params, $checkTickets);
        if (!$validate->original['status']) return $validate;
        $dataTickets = $validate->original['data']['detail'];

        // Si no se manda monto no se verifica
        if (isset($params['amount'])) {

            $total_amount = 0;
            foreach ($dataTickets as $key => $ticket) {
                if ($ticket['issued_proof_payment'] != 1) {
                    $total_amount += $ticket['amount_document'];
                } else {
                    $total_amount += ($ticket['amount_document'] - $ticket['proof_payment_amount']);
                }
            }

            // Verificiar que el monto sea igual o menor que los tickets seleccionados
            if ((float)$proofPayment['amount'] > (float)$total_amount) return $this->errorResponse('El monto ingresado es mayor a los documentos seleccionados.', 400);
        }

        // Número de comprobante de pago por empresa
        $data = [
            'proof_number'      => $proofPayment['id'],
            'company_rut'       => $company->rut,
            'company_name'      => $company->business_name,
            'client_rut'        => $dataUserClient['rut'],
            'client_name'       => $dataUserClient['name'],
            'client_phone'      => $dataUserClient['phone'],
            'date'              => date('Y-m-d'),
            'month'             => date('m'),
            'year'              => date('y'),
            'dataTickets'       => $dataTickets,
            'way_to_pay'        => $way_to_pay,
            'date_admission'    => $proofPayment['date_admission'],
            'document_date'     => $proofPayment['document_date'],
            'bank_name'         => Bank::find($proofPayment['idbank'])->name ?? null,
            'serie'             => $proofPayment['serie'],
            'amount'            => $proofPayment['amount'],
            'amount_text'       => $proofPayment['amount_text'],
            'unpaid_docs'       => $proofPayment['unpaid_documents'],
            'user'              => $dataUser['name'],
            'date_creation'     => $dateTime->format('Y-m-d'),
            'hour_creation'     => $dateTime->format('H:i:s'),
            'authorized_quota'  => null,
            'used_quota'        => null,
            'available_space'   => null,
            'expiration'        => $proofPayment['expiration'],
            'entered_by'        => $dataUser['name'],
            'reconciled_by'     => $reconciledByUserData['name'] ?? '-'
        ];

        return $this->successResponse('OK', $data);
    }

    private function dataTickets($params, $tickets)
    {
        $data = [];

        foreach ($tickets as $key => $ticket) {
            if ($ticket->issued_proof_payment == 1) {
                $remainingBalance = $ticket->total - $ticket->proof_payment_amount;

                // En caso que el ticket ya haya sido mandado a comprobante paso a verificar que tenga dispoible monto a pagar
                if ($ticket->total == $ticket->proof_payment_amount) {
                    return $this->errorResponse('El documento con posición ' . $key + 1 . ' seleccionado se encuentra cubierto en su totalidad.', 400);
                }

                if ($params['payment_ticket'][$key] > $remainingBalance) {
                    return $this->errorResponse('El monto de ' . $params['payment_ticket'][$key] . ' para el pago del ticket es mayor al saldo restante: ' . $remainingBalance . ' . Por favor verifique.', 400);
                }
            } //  Verificar que los montos a pagar no sea mayor al monto del ticket
            elseif ($params['payment_ticket'][$key] > $ticket->total) {
                return $this->errorResponse('El monto a pagar por un documento no puede ser mayor que el total del mismo.', 400);
            }

            $data[] = [
                'n_doc'             => $ticket->id,
                'type_document'     => $ticket->typeDocument->name,
                'date_document'     => $ticket->date,
                'amount_document'   => $ticket->total,
                'payment_ticket'    => $params['payment_ticket'][$key],
                //Data extra
                'issued_proof_payment'  => $ticket->issued_proof_payment,
                'proof_payment_amount'  => $ticket->proof_payment_amount
            ];
        }

        $totalPartialPayments = array_sum($params['payment_ticket']);
        if ($totalPartialPayments > $params['amount']) {
            return $this->errorResponse('El total de los pagos parciales no puede ser mayor al pago total a realizar.', 400);
        }

        return $this->successResponse('OK', $data);
    }
    private function dataUser($rut, $typeUser)
    {

        switch ($typeUser) {
            case '1':
                # Superadministrador
                $name = 'Superadministrador';
                $rut = '00000000-0';
                $phone = '999999999';

                break;

            case '2':
                # Administrador
                $infoUser = AdminCompany::where('rut', $rut)->company()->first();
                $name = $infoUser->name ?? 'Nombre no disponible';
                $rut = $infoUser->rut ?? '00000000-0';
                $phone = $infoUser->phone ?? '999999999';
                break;

            case '3':
                # Vendedor
                $infoUser = Salesman::where('rut', $rut)->company()->first();
                $name = $infoUser->name ?? 'Nombre no disponible';
                $rut = $infoUser->rut ?? '00000000-0';
                $phone = $infoUser->phone ?? '999999999';
                break;

            case '4':
                # Proveedor
                $infoUser = Supplier::where('rut', $rut)->company()->first();
                $name = $infoUser->name_rz ?? 'Nombre no disponible';
                $rut = $infoUser->rut ?? '00000000-0';
                $phone = $infoUser->phone ?? '999999999';
                break;

            case '5':
                # Cliente
                $infoUser = Supplier::where('rut', $rut)->company()->first();
                if (!$infoUser) {
                    $salesman = true;
                    $infoUser = Salesman::where('rut', $rut)->company()->first();
                }
                $name = ($salesman ? $infoUser->name : $infoUser->name_rz) ?? 'Nombre no disponible';
                $rut = $infoUser->rut ?? '00000000-0';
                $phone = $infoUser->phone ?? '999999999';
                break;

            default:
                # code...
                break;
        }

        return [
            'name'  => $name ?? 'Nombre no disponible',
            'rut'   => $rut ?? '00000000-0',
            'phone' => $phone ?? '999999999',
        ];
    }

    private function checkFKandInitialStructure($params)
    {
        // Verificar tickets que hayan sido emitidos
        $validate = $this->checkTickets($params);
        if (!$validate->original['status']) return $validate;
        $checkTickets = $validate->original['data']['detail'];

        $validate = $this->dataTickets($params, $checkTickets);
        if (!$validate->original['status']) return $validate;
        $dataTickets = $validate->original['data']['detail'];

        $total_amount = array_sum(array_column($dataTickets, 'amount_document'));
        $amount = isset($params['amount']) ? $params['amount'] : $total_amount;

        // Veririficar cliente
        $detailUser = DetailUserCompany::where('iduser', $params['idclient'])
            ->company()->active()->first();
        if (!$detailUser) return $this->errorResponse('El cliente seleccionado es inválido.', 400);

        // Estructura de detalle del método de pago seleccionado
        $wayToPayForCompany = WayToPayForCompany::find($params['idway_to_pay_for_company']);
        $paymentMethod = $wayToPayForCompany->wayToPay->SII_fmapago;

        $expiration = null;

        // Añadir días de expiración en caso sea crédito
        if ($wayToPayForCompany->wayToPay->SII_fmapago == 2) {
            // Extraer los días en el nombre del método de pago
            $validate = $this->checkNameCredit($wayToPayForCompany->wayToPay->name);
            if (!$validate->original['status']) return $validate;
            $numberDaysWayToPayCredit = $validate->original['data']['detail'];
            $expiration = Carbon::now()->addDays($numberDaysWayToPayCredit);
        }

        // País
        $company = Company::where('id', $params['idcompany'])->first();
        $coin = $company->district->province->department->country->coin;
        $subdivisionCoin = $company->district->province->department->country->subdivision_coin;

        /* Verificar monto valido */
        // Patrón para verificar un número decimal con tres decimales
        $pattern = '/^\d+(\.\d{1,3})?$/';
        $correctAmount = preg_match($pattern, $amount);
        if (!$correctAmount) return $this->errorResponse('El monto ingresado es inválido.', 400);

        // Documentos impagos
        $unpaidDocs = Ticket::whereNotIn('id', $params['idstickets'])->where('idcustomer', $params['idclient'])
            ->where('payment_status', 2)->active()->count();

        // Pasar monto a texto
        $amountText = $this->numbetToWords($amount, $coin, $subdivisionCoin);

        $data = [
            'payment_method'    => $paymentMethod,
            'amount_text'       => $amountText,
            'expiration'        => $expiration,
            'unpaid_documents'  => $unpaidDocs
        ];

        return $this->successResponse('OK', $data);
    }

    private function checkNameCredit($nameCredit)
    {
        // Definir la expresión regular
        $regex = '/(\d+)\s+(?:DIA|d[ií]a[s]?)\b/i'; // DIAS, días o dias

        // Realizar la coincidencia con la expresión regular
        if (preg_match($regex, $nameCredit, $coincidences)) {
            // Obtener el número de días
            $numberDays = $coincidences[1];
        } else return $this->errorResponse('El nombre del método de pago no es valido.', 400);


        return $this->successResponse('OK',  $numberDays);
    }

    private function numbetToWords($amount, $coin, $subdivisionCoin)
    {
        /* Separar enteros de decimales */
        // Explora el número usando la función explode
        $part = explode('.', $amount);

        // Extrae la parte entera y la parte decimal (si existe)
        $partEntire = isset($part[0]) ? intval($part[0]) : false;
        $parteDecimal = isset($part[1]) ? intval($part[1]) : false;

        /* Pasar de número a texto */
        // Crear el número de palabras clase "manager"
        $numberToWords = new NumberToWords();

        // Construir un nuevo transformador numérico usando el identificador de lenguaje RFC 3066
        $numberTransformer = $numberToWords->getNumberTransformer('es');

        $entire =  $numberTransformer->toWords($partEntire);

        if (!is_null($subdivisionCoin)) {
            $decimal =  $numberTransformer->toWords($parteDecimal);
            $finalText = strtoupper("$entire $coin con $decimal $subdivisionCoin");
        } else {
            $finalText = strtoupper("$entire $coin");
        }

        return $finalText;
    }

    private function checkTickets($params)
    {
        $dataTickets = Ticket::whereIn('id', $params['idstickets'])
            ->where('idcustomer', $params['idclient'])
            ->where('status_invoice', 1)->active()
            ->get();

        if ($dataTickets->count() != count($params['idstickets'])) return $this->errorResponse('Selección inválida de tickets o no han sido emitidos.', 400);

        return $this->successResponse('OK',  $dataTickets);
    }

    public function sendEmail($id, $params)
    {
        try {
            DB::beginTransaction();
            #Validar comprobante de pago
            $proofPayment = ProofPayment::activeForID($id)->where('payment_method', 2)->company()->first();
            if (!$proofPayment) return $this->errorResponse('Comprobante de pago no válido.', 400);

            // Verificar si el correo ya ha sido enviado
            if ($proofPayment->expiration_email == 0) {
                // Enviar correo
                $sendMail = $this->mailSendingMethod($proofPayment);
                if (!$sendMail->original['status']) return $sendMail;

                $proofPayment->update(['expiration_email' => 1]);
            } else {
                // Verificar si se enviará o no
                if (!isset($params['resend_email']))  return $this->errorResponse('El correo ya fue enviado previamente. ¿Desea enviarlo nuevamente?', 400);

                // Enviar correo nuevamente
                $sendMail = $this->mailSendingMethod($proofPayment);
                if (!$sendMail->original['status']) return $sendMail;
            }

            DB::commit();
            return $this->successResponse('El correo fue enviado exitosamente.');
        } catch (\Throwable $th) {
            DB::rollBack();
            return $this->externalError('durante el envio de un correo.', $th->getMessage());
        }
    }

    private function mailSendingMethod($proofPayment)
    {
        try {

            $user = DetailUserCompany::where('iduser', $proofPayment->userCreated->id)
                ->company()->active()->first();
            if (!$user) return $this->errorResponse('El usuario no cuenta con información válida.', 400);

            Mail::to($proofPayment->userCreated->email)->send(
                new ProofPaymentMail(
                    $this->dataUser($proofPayment->userCreated->rut, $user->iduser_type)['name'],
                    $proofPayment->id,
                    Carbon::parse($proofPayment->expiration)->isoFormat('D [de] MMMM [de] YYYY'),
                    $proofPayment->amount_text
                )
            );
            return $this->successResponse('OK');
        } catch (\Throwable $th) {
            return $this->externalError('durante el envio de un correo.', $th->getMessage());
        }
    }
}
