<?php

class FactoDocumentResponseParser
{
    private $response;

    public function __construct(array $response)
    {
        $this->response = $response;
    }

    public function response()
    {
        return $this->response;
    }

    public function resultStatus()
    {
        $result = $this->findFirstArrayByKey($this->response, 'result');
        if (!is_array($result) || !array_key_exists('status', $result)) {
            return null;
        }
        return (int)$result['status'];
    }

    public function businessError()
    {
        $result = $this->findFirstArrayByKey($this->response, 'result');
        if (is_array($result)) {
            $message = trim((string)($result['error_message'] ?? ''));
            if ($message !== '') {
                return $message;
            }
            if (array_key_exists('status', $result) && (int)$result['status'] !== 0) {
                return 'FACTO rechazó la operación con estado ' . (int)$result['status'] . '.';
            }
        }

        foreach (array('error', 'message', 'error_message') as $key) {
            if (isset($this->response[$key]) && is_scalar($this->response[$key])) {
                $message = trim((string)$this->response[$key]);
                if ($message !== '') {
                    return $message;
                }
            }
        }
        return null;
    }

    public function assertSuccess($context = 'operación')
    {
        $error = $this->businessError();
        if ($error !== null) {
            throw new RuntimeException($error);
        }
        $status = $this->resultStatus();
        if ($status !== null && $status !== 0) {
            throw new RuntimeException('FACTO no aprobó la ' . $context . '.');
        }
    }

    public function documentId()
    {
        $value = $this->findScalarByKeys($this->response, array('document_id'));
        return $this->normalizeIdentifier($value);
    }

    public function documentNumber()
    {
        $header = $this->header();
        if (isset($header['document_number'])) {
            return $this->normalizeIdentifier($header['document_number']);
        }
        $value = $this->findScalarByKeys($this->response, array('document_number', 'folio'));
        return $this->normalizeIdentifier($value);
    }

    public function header()
    {
        $header = $this->findFirstArrayByKey($this->response, 'header');
        return is_array($header) ? $header : array();
    }

    public function totals()
    {
        $totals = $this->findFirstArrayByKey($this->response, 'totals');
        return is_array($totals) ? $totals : array();
    }

    public function details()
    {
        $details = $this->findFirstArrayByKey($this->response, 'details');
        return is_array($details) ? $details : array();
    }

    public function references()
    {
        $refs = $this->findFirstArrayByKey($this->response, 'references');
        return is_array($refs) ? $refs : array();
    }

    public function electronicDocument()
    {
        $electronic = $this->findFirstArrayByKey($this->response, 'electronic_document');
        if (is_array($electronic)) {
            return $electronic;
        }

        $fallback = array();
        foreach (array('document_pdf', 'document_xml', 'stamp_img') as $key) {
            $value = $this->findScalarByKeys($this->response, array($key));
            if ($value !== null && $value !== '') {
                $fallback[$key] = $value;
            }
        }
        return $fallback;
    }

    public function hasPdf()
    {
        $electronic = $this->electronicDocument();
        return !empty($electronic['document_pdf']);
    }

    public function hasXml()
    {
        $electronic = $this->electronicDocument();
        return !empty($electronic['document_xml']);
    }

    public function assertPreviewReady()
    {
        $this->assertSuccess('previsualización');
        if ($this->documentId() === null) {
            throw new RuntimeException('FACTO aprobó la previsualización, pero no devolvió document_id. No se habilitó la emisión.');
        }
        if (!$this->hasPdf()) {
            throw new RuntimeException('FACTO no devolvió PDF de previsualización. No se habilitó la emisión.');
        }
    }

    public function assertIssuedReady()
    {
        $this->assertSuccess('emisión');
        if ($this->documentId() === null) {
            throw new RuntimeException('FACTO aprobó la emisión, pero no devolvió document_id. La operación quedó bloqueada para evitar duplicados.');
        }
    }

    public function safeResponse()
    {
        $copy = $this->response;
        $this->walkAndStripBinary($copy);
        return $copy;
    }

    private function findFirstArrayByKey($value, $needle)
    {
        if (!is_array($value)) {
            return null;
        }
        if (array_key_exists($needle, $value) && is_array($value[$needle])) {
            return $value[$needle];
        }
        foreach ($value as $item) {
            if (is_array($item)) {
                $found = $this->findFirstArrayByKey($item, $needle);
                if (is_array($found)) {
                    return $found;
                }
            }
        }
        return null;
    }

    private function findScalarByKeys($value, array $keys)
    {
        if (!is_array($value)) {
            return null;
        }
        foreach ($keys as $key) {
            if (array_key_exists($key, $value) && !is_array($value[$key]) && $value[$key] !== null && $value[$key] !== '') {
                return $value[$key];
            }
        }
        foreach ($value as $item) {
            if (is_array($item)) {
                $found = $this->findScalarByKeys($item, $keys);
                if ($found !== null) {
                    return $found;
                }
            }
        }
        return null;
    }

    private function normalizeIdentifier($value)
    {
        if ($value === null || $value === '') {
            return null;
        }
        return trim((string)$value);
    }

    private function walkAndStripBinary(&$value)
    {
        if (!is_array($value)) {
            return;
        }
        foreach ($value as $key => &$item) {
            if (in_array((string)$key, array('document_pdf', 'document_xml', 'stamp_img'), true)) {
                $item = is_string($item) && $item !== '' ? '[BASE64 OMITIDO]' : $item;
                continue;
            }
            if (is_array($item)) {
                $this->walkAndStripBinary($item);
            }
        }
    }
}
