<?php

namespace App\Services;

use App\Models\Order;
use App\Models\Setting;
use Greenter\Model\Client\Client;
use Greenter\Model\Company\Address;
use Greenter\Model\Company\Company;
use Greenter\Model\Sale\Invoice;
use Greenter\Model\Sale\SaleDetail;
use Greenter\Model\Sale\Legend;
use Greenter\Ubl\UblValidator;
use Greenter\See;

/**
 * Servicio de facturación electrónica SUNAT (Perú) usando greenter/greenter.
 *
 * Genera el XML UBL 2.1, lo firma con el certificado digital y lo envía a
 * SUNAT. Los productos van como líneas de venta y el handling/delivery se
 * añaden como líneas de "servicio" separadas, tal como exige la operación.
 *
 * Requiere configurar en .env:
 *   SUNAT_MODE, SUNAT_RUC, SUNAT_SOL_USER, SUNAT_SOL_PASS, SUNAT_CERT_PATH
 */
class SunatService
{
    private const IGV_RATE = 0.18;

    private function see(): See
    {
        $see = new See();
        $see->setCertificate(file_get_contents(base_path(config('sunat.cert_path'))));
        $see->setService(
            config('sunat.mode') === 'produccion'
                ? 'https://e-factura.sunat.gob.pe/ol-ti-itcpfegem/billService'
                : 'https://e-beta.sunat.gob.pe/ol-ti-itcpfegem/billService'
        );
        $see->setClaveSOL(
            config('sunat.ruc'),
            config('sunat.sol_user'),
            config('sunat.sol_pass')
        );
        return $see;
    }

    /** Empresa emisora (datos del comercio). */
    private function company(): Company
    {
        return (new Company())
            ->setRuc(config('sunat.ruc'))
            ->setRazonSocial(Setting::get('company_name', config('app.name')))
            ->setNombreComercial(Setting::get('company_name', config('app.name')))
            ->setAddress((new Address())
                ->setUbigueo(Setting::get('company_ubigeo', '150101'))
                ->setDireccion(Setting::get('company_address', 'Lima')));
    }

    /**
     * Construye y emite la factura/boleta electrónica de un pedido.
     * Devuelve el CDR (constancia) o lanza excepción si falla.
     */
    public function emitInvoice(Order $order): array
    {
        // Cliente: RUC(6) -> factura (01); DNI(1) u otros -> boleta (03).
        $tipoDoc = $order->customer_doc_type === '6' ? '01' : '03';
        $serie   = $tipoDoc === '01' ? 'F001' : 'B001';

        $client = (new Client())
            ->setTipoDoc($order->customer_doc_type)
            ->setNumDoc($order->customer_doc_number ?: '00000000')
            ->setRznSocial($order->customer_name);

        // ---- Detalles: primero los productos ----
        $details = [];
        foreach ($order->items as $item) {
            $details[] = $this->buildLine(
                $item->product_name,
                (float) $item->price,
                (int) $item->quantity
            );
        }

        // ---- Líneas de servicio separadas: handling y delivery ----
        if ($order->handling_fee > 0) {
            $details[] = $this->buildLine('Servicio de empaque (handling)', (float) $order->handling_fee, 1, 'SERV');
        }
        if ($order->delivery_fee > 0) {
            $details[] = $this->buildLine('Servicio de envío (delivery)', (float) $order->delivery_fee, 1, 'SERV');
        }

        $mtoOperGravadas = $order->subtotal;
        $mtoIGV          = $order->igv;

        $invoice = (new Invoice())
            ->setUblVersion('2.1')
            ->setTipoOperacion('0101')      // Venta interna
            ->setTipoDoc($tipoDoc)
            ->setSerie($serie)
            ->setCorrelativo((string) $order->id)
            ->setFechaEmision(new \DateTime())
            ->setTipoMoneda('PEN')
            ->setCompany($this->company())
            ->setClient($client)
            ->setMtoOperGravadas($mtoOperGravadas)
            ->setMtoIGV($mtoIGV)
            ->setTotalImpuestos($mtoIGV)
            ->setValorVenta($mtoOperGravadas)
            ->setSubTotal($order->total)
            ->setMtoImpVenta($order->total)
            ->setDetails($details)
            ->setLegends([
                (new Legend())->setCode('1000')->setValue($this->numeroALetras($order->total)),
            ]);

        // Validación UBL previa (opcional pero recomendada).
        $errors = (new UblValidator())->validate($invoice);
        if ($errors) {
            throw new \RuntimeException('XML UBL inválido: ' . $errors);
        }

        // Firmar + enviar.
        $see    = $this->see();
        $result = $see->send($invoice);

        // Guardar el XML firmado.
        $xml = $see->getFactory()->getLastXml();
        \Storage::put("sunat/{$serie}-{$order->id}.xml", $xml);

        if (! $result->isSuccess()) {
            $order->update(['sunat_status' => 'error']);
            throw new \RuntimeException('SUNAT rechazó: ' . $result->getError()->getMessage());
        }

        $order->update(['sunat_status' => 'aceptado']);

        return [
            'xml' => $xml,
            'cdr' => $result->getCdrResponse(),
        ];
    }

    /** Crea una línea de venta (producto o servicio) con IGV. */
    private function buildLine(string $desc, float $priceConIgv, int $qty, string $unit = 'NIU'): SaleDetail
    {
        // El precio guardado ya incluye IGV -> separamos valor unitario.
        $valorUnit = round($priceConIgv / (1 + self::IGV_RATE), 6);
        $valorVenta = round($valorUnit * $qty, 2);
        $igv        = round($valorVenta * self::IGV_RATE, 2);

        return (new SaleDetail())
            ->setCodProducto('')
            ->setUnidad($unit)
            ->setCantidad($qty)
            ->setDescripcion($desc)
            ->setMtoValorUnitario($valorUnit)
            ->setMtoValorVenta($valorVenta)
            ->setMtoBaseIgv($valorVenta)
            ->setPorcentajeIgv(18.00)
            ->setIgv($igv)
            ->setTipAfeIgv('10')                 // Gravado - Operación onerosa
            ->setTotalImpuestos($igv)
            ->setMtoPrecioUnitario($priceConIgv);
    }

    /** Conversión de monto a letras (simplificada; reemplazar por librería si se requiere). */
    private function numeroALetras(float $monto): string
    {
        $entero = (int) floor($monto);
        $decimal = (int) round(($monto - $entero) * 100);
        return "SON {$entero} CON {$decimal}/100 SOLES";
    }
}
