<?php
/** Canonical identities for the immutable WooCommerce fiscal documents. */
if (!defined('ABSPATH')) {
    exit;
}

class VerifactuDocumentIdentity
{
    private static function documents()
    {
        return new DdvWooDocumentRepository();
    }

    public static function getInvoices($order)
    {
        $order = self::assertOrder($order);
        return self::documents()->getInvoicesByOrder((int) $order->get_id());
    }

    public static function getInvoiceById($idOrderInvoice)
    {
        $invoice = self::documents()->getInvoiceById((int) $idOrderInvoice);
        if (!$invoice) {
            throw new Exception('La factura fiscal WooCommerce ' . (int) $idOrderInvoice . ' no existe o aun no esta emitida.');
        }
        self::assertSnapshot($invoice);
        return $invoice;
    }

    public static function getInvoice($order, $invoice = null)
    {
        $order = self::assertOrder($order);
        if ($invoice instanceof DdvWooFiscalDocument) {
            if ($invoice->document_type !== DdvWooDocumentRepository::TYPE_INVOICE || (int) $invoice->id_order !== (int) $order->get_id()) {
                throw new Exception('La factura indicada no pertenece al pedido WooCommerce.');
            }
            self::assertSnapshot($invoice);
            return $invoice;
        }
        if (is_numeric($invoice) && (int) $invoice > 0) {
            $loaded = self::getInvoiceById((int) $invoice);
            if ((int) $loaded->id_order !== (int) $order->get_id()) {
                throw new Exception('La factura indicada no pertenece al pedido WooCommerce.');
            }
            return $loaded;
        }
        $invoices = self::getInvoices($order);
        if (count($invoices) === 0) {
            throw new Exception('El pedido no tiene una factura fiscal emitida por ddverifactu.');
        }
        if (count($invoices) !== 1) {
            throw new Exception('El pedido contiene mas de una factura fiscal; seleccione el documento exacto.');
        }
        return $invoices[0];
    }

    public static function getInvoiceNumber($order, $invoice = null)
    {
        $invoice = self::getInvoice($order, $invoice);
        return self::validateNumber($invoice->number, 'numero de factura');
    }

    public static function getInvoiceIssueDate($invoice, $format = 'd-m-Y')
    {
        if (!($invoice instanceof DdvWooFiscalDocument)) {
            $invoice = self::getInvoiceById((int) $invoice);
        }
        $date = DateTimeImmutable::createFromFormat('!d-m-Y', (string) $invoice->issue_date, VerifactuConfig::getTimezone());
        if (!$date) {
            throw new Exception('La factura fiscal no conserva una fecha de expedicion valida.');
        }
        return $date->format($format);
    }

    public static function getInvoiceIdentity($order, $invoice = null)
    {
        $invoice = self::getInvoice($order, $invoice);
        $number = self::getInvoiceNumber($order, $invoice);
        $date = self::getInvoiceIssueDate($invoice);
        return array(
            'number' => $number,
            'issue_date' => $date,
            'file_token' => self::safeFileToken($number . '_' . $date),
        );
    }

    public static function getCreditSlip($order, $idOrderSlip)
    {
        $order = self::assertOrder($order);
        $refund = wc_get_order((int) $idOrderSlip);
        if (!($refund instanceof WC_Order_Refund) || (int) $refund->get_parent_id() !== (int) $order->get_id()) {
            throw new Exception('El abono WooCommerce ' . (int) $idOrderSlip . ' no existe o no pertenece al pedido.');
        }
        return $refund;
    }

    public static function getCreditSlipNumber($order, $creditSlip)
    {
        $order = self::assertOrder($order);
        $refund = $creditSlip instanceof WC_Order_Refund ? $creditSlip : self::getCreditSlip($order, (int) $creditSlip);
        $doc = self::documents()->getCreditNoteByRefund((int) $refund->get_id());
        if (!$doc) {
            throw new Exception('El abono WooCommerce aun no tiene un documento fiscal rectificativo emitido.');
        }
        self::assertSnapshot($doc);
        return self::validateNumber($doc->number, 'numero de rectificativa');
    }

    public static function getCreditSlipIdentity($order, $idOrderSlip)
    {
        $order = self::assertOrder($order);
        $refund = self::getCreditSlip($order, (int) $idOrderSlip);
        $doc = self::documents()->getCreditNoteByRefund((int) $refund->get_id());
        if (!$doc) {
            throw new Exception('El abono WooCommerce aun no tiene documento fiscal rectificativo.');
        }
        self::assertSnapshot($doc);
        $number = self::validateNumber($doc->number, 'numero de rectificativa');
        return array(
            'number' => $number,
            'issue_date' => (string) $doc->issue_date,
            'file_token' => self::safeFileToken($number . '_' . $doc->issue_date),
        );
    }

    public static function getCreditSlipSourceInvoices($order, $creditSlip)
    {
        $order = self::assertOrder($order);
        if (!($creditSlip instanceof WC_Order_Refund)) {
            $creditSlip = self::getCreditSlip($order, (int) $creditSlip);
        }
        $invoice = self::documents()->getInvoiceByOrder((int) $order->get_id());
        if (!$invoice) {
            throw new Exception('El pedido original no tiene una factura fiscal emitida.');
        }
        return array($invoice);
    }

    public static function getCreditDocument($idRefund)
    {
        $doc = self::documents()->getCreditNoteByRefund((int) $idRefund);
        if (!$doc) {
            throw new Exception('No existe el documento fiscal del abono ' . (int) $idRefund . '.');
        }
        self::assertSnapshot($doc);
        return $doc;
    }

    public static function safeFileToken($value)
    {
        $value = preg_replace('/[^A-Za-z0-9._-]+/', '_', (string) $value);
        return trim(substr($value, 0, 100), '._-');
    }

    private static function assertOrder($order)
    {
        if (is_numeric($order)) {
            $order = wc_get_order((int) $order);
        }
        if (!($order instanceof WC_Order) || $order instanceof WC_Order_Refund) {
            throw new Exception('No se ha podido cargar el pedido WooCommerce.');
        }
        return $order;
    }

    private static function validateNumber($number, $label)
    {
        $number = trim((string) $number);
        if ($number === '' || strlen($number) > 60 || preg_match('/[\x00-\x1F\x7F]/', $number)) {
            throw new Exception('El ' . $label . ' no es valido para VeriFactu.');
        }
        return $number;
    }

    private static function assertSnapshot(DdvWooFiscalDocument $doc)
    {
        $json = (string) $doc->snapshot_json;
        if ($json === '' || !is_array($doc->snapshot) || !hash_equals(strtolower((string) $doc->snapshot_sha256), hash('sha256', $json))) {
            throw new Exception('La instantanea fiscal inmutable del documento no supera la comprobacion de integridad.');
        }
    }
}
