<?php

namespace App\Modules\Billing\Uyumsoft;

use App\Modules\Billing\BaseBillingModule;
use App\Modules\Billing\Support\UblTrInvoiceBuilder;

/**
 * Uyumsoft e-fatura / e-arşiv SOAP entegratör modülü.
 *
 * Uyumsoft, GİB özel-entegratörü olarak IIntegration SOAP servisini sunar
 * (WSDL prod: https://efatura.uyumsoft.com.tr/Services/Integration?wsdl,
 * test: https://efatura-test.uyumsoft.com.tr/services/Integration, creds
 * Uyumsoft/Uyumsoft). Kimlik doğrulama WS-Security UsernameToken SoapHeader
 * ile her istekte gider (oturum/token yoktur → beforeRequest gereksiz).
 *
 * Fatura gövdesi UBL-TR 1.2 XML'dir; ortak UblTrInvoiceBuilder üretir ve
 * SendInvoice operasyonuna gömülü olarak (base64) iletilir.
 *
 * Referans: github.com/alperuluses/Uyumsoft-E-fatura-Entegrasyon + resmî
 * IIntegration WSDL.
 *
 * IIntegration operasyonları (kullanılanlar):
 *   - SendInvoice(invoices)          : UBL-TR fatura gönder
 *   - GetOutboxInvoiceList(query)    : giden kutusu listesi (durum/getir)
 *   - GetInboxInvoiceList(query)     : gelen kutusu listesi
 *   - ChangeInvoiceArchiveStatus(...): e-arşiv arşiv durumu
 *   - IsEInvoiceUser(vkn)            : VKN e-fatura mükellefi mi (test için)
 *   - GetSystemDate()                : bağlantı sağlık kontrolü (fallback)
 *
 * GÜVENLİK: username/password/ham SOAP yanıtı/SESSION ASLA loglanmaz
 * (callSoap yalnız exception tipini damgalar). TLS verify açık (SoapClient
 * default). SoapFault → fail-closed (success=false).
 */
class UyumsoftModule extends BaseBillingModule
{
    /**
     * Uyumsoft IIntegration servis namespace URI'si (SOAP body/header için).
     * WS-Security header standart OASIS namespace'lerini kullanır.
     */
    private const WSSE_NS = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd';

    public function getName(): string
    {
        return 'uyumsoft';
    }

    public function getDisplayName(): string
    {
        return __('admin/modules.uyumsoft_display_name');
    }

    public function getType(): string
    {
        return 'e-invoice';
    }

    public function getDefaultSettings(): array
    {
        return [
            'wsdl_url' => 'https://efatura.uyumsoft.com.tr/Services/Integration?wsdl',
            'username' => '',
            'password' => '',
            'supplier_vkn' => '',
            'supplier_title' => '',
            'supplier_tax_office' => '',
            'supplier_address' => '',
            'supplier_city' => '',
        ];
    }

    public function test(): array
    {
        try {
            // Bağlantı + kimlik doğrulama sağlığı: IsEInvoiceUser (satıcı kendi
            // VKN'i ile) → auth header'ı gerçekten çalışıyor mu. VKN boşsa
            // GetSystemDate ile salt-bağlantı doğrula (auth yine header'da).
            $vkn = trim((string) ($this->settings['supplier_vkn'] ?? ''));

            if ($vkn !== '') {
                $response = $this->soapCall('IsEInvoiceUser', ['vkn' => $vkn]);
            } else {
                $response = $this->soapCall('GetSystemDate', []);
            }

            if (($response['success'] ?? true) === false) {
                return [
                    'success' => false,
                    'message' => __('admin/modules.uyumsoft_connection_error', ['error' => (string) ($response['error'] ?? 'unknown')]),
                ];
            }

            return [
                'success' => true,
                'message' => __('admin/modules.uyumsoft_connection_success'),
            ];

        } catch (\Throwable $e) {
            return [
                'success' => false,
                'message' => __('admin/modules.uyumsoft_connection_error', ['error' => $e->getMessage()]),
            ];
        }
    }

    public function createEInvoice(array $invoiceData): array
    {
        return $this->sendUblInvoice($invoiceData, 'TICARIFATURA', 'SATIS', 'einvoice');
    }

    public function createEArchive(array $invoiceData): array
    {
        return $this->sendUblInvoice($invoiceData, 'EARSIVFATURA', 'SATIS', 'earchive');
    }

    /**
     * Ortak UBL-TR üretimi + SendInvoice gönderimi.
     *
     * @param  string  $profileId  UBL profil (TICARIFATURA / EARSIVFATURA)
     * @param  string  $typeCode   Fatura tipi (SATIS / IADE)
     * @param  string  $kind       İç sınıflandırma (einvoice/earchive/refund) —
     *                             yalnız hata mesajı çeviri anahtarı için
     */
    private function sendUblInvoice(array $invoiceData, string $profileId, string $typeCode, string $kind): array
    {
        try {
            $xml = (new UblTrInvoiceBuilder)->build(
                $invoiceData,
                $this->supplier(),
                $profileId,
                $typeCode
            );

            // SendInvoice: UBL-TR XML'i base64 gömülü olarak invoices dizisinde
            // iletilir (referans imzası: invoices[] içinde Content + FileName).
            $response = $this->soapCall('SendInvoice', $this->buildSendInvoiceArgs($invoiceData, $xml));

            if (($response['success'] ?? true) === false) {
                return [
                    'success' => false,
                    'message' => __("admin/modules.uyumsoft_{$kind}_create_error", ['error' => (string) ($response['error'] ?? 'unknown')]),
                ];
            }

            // Uyumsoft senkron kabul → giden kutusu id/uuid'i external_id olur.
            // Yanıt şekli integrasyon sözleşmesine göre değişebildiğinden çok
            // olası anahtarları sırayla dener; bulunamazsa fatura numarasına düşer.
            $externalId = $this->extractInvoiceId($response, (string) ($invoiceData['invoice_number'] ?? ''));

            // İş kabul edildi ancak GİB tarafı durumu sonradan sonuçlanabilir:
            // durum sorgusu getInvoice/checkInvoiceStatus ile yapılır.
            return [
                'success' => true,
                'external_id' => $externalId,
                'pending' => true,
                'status' => 'pending',
                'data' => $response,
            ];

        } catch (\Throwable $e) {
            return [
                'success' => false,
                'message' => __("admin/modules.uyumsoft_{$kind}_create_error", ['error' => $e->getMessage()]),
            ];
        }
    }

    public function cancelInvoice(string $externalId): array
    {
        try {
            // Uyumsoft'ta e-fatura iptali GİB kurallarına tabidir: giden fatura
            // (TEMEL/TİCARİ) alıcı red'i ile, e-arşiv ise ChangeInvoiceArchiveStatus
            // ile iptal-işaretlenir. Genel iptal operasyonu SetDocumentStatus/
            // CancelInvoice olarak sunulur; referansa göre CancelEArchiveInvoice
            // e-arşiv iptalidir. Burada e-arşiv iptal yolunu kullanıyoruz;
            // e-fatura iptali (alıcı-red akışı) modül dışıdır.
            $response = $this->soapCall('CancelEArchiveInvoice', [
                'invoiceId' => $externalId,
                'reason' => __('admin/modules.uyumsoft_field_cancel_reason'),
            ]);

            if (($response['success'] ?? true) === false) {
                return [
                    'success' => false,
                    'message' => __('admin/modules.uyumsoft_invoice_cancel_error', ['error' => (string) ($response['error'] ?? 'unknown')]),
                ];
            }

            return [
                'success' => true,
                'message' => __('admin/modules.uyumsoft_invoice_cancelled_success'),
                'data' => $response,
            ];

        } catch (\Throwable $e) {
            return [
                'success' => false,
                'message' => __('admin/modules.uyumsoft_invoice_cancel_error', ['error' => $e->getMessage()]),
            ];
        }
    }

    public function refundInvoice(string $externalId, array $refundData): array
    {
        // İade = yeni bir IADE tipli fatura düzenle (GİB'de iade faturası ayrı
        // belgedir). Orijinal fatura id'sini not için taşır.
        $refundData['refunded_invoice_id'] = $externalId;

        return $this->sendUblInvoice($refundData, 'TICARIFATURA', 'IADE', 'refund');
    }

    public function getInvoice(string $externalId): array
    {
        try {
            // Giden kutusundan id/uuid filtresi ile fatura durumunu getir.
            $response = $this->soapCall('GetOutboxInvoiceList', [
                'query' => ['Id' => $externalId],
            ]);

            if (($response['success'] ?? true) === false) {
                return [
                    'success' => false,
                    'message' => __('admin/modules.uyumsoft_invoice_fetch_error', ['error' => (string) ($response['error'] ?? 'unknown')]),
                ];
            }

            return [
                'success' => true,
                'data' => $response,
            ];

        } catch (\Throwable $e) {
            return [
                'success' => false,
                'message' => __('admin/modules.uyumsoft_invoice_fetch_error', ['error' => $e->getMessage()]),
            ];
        }
    }

    /**
     * Asenkron durum sorgusu: GetOutboxInvoiceList sonucundaki GİB statüsünü
     * pending/approved/rejected'e map et.
     *
     * @return array{success: bool, status: 'pending'|'approved'|'rejected'}
     */
    public function checkInvoiceStatus(string $externalId, array $params = []): array
    {
        try {
            $response = $this->soapCall('GetOutboxInvoiceList', [
                'query' => ['Id' => $externalId],
            ]);

            if (($response['success'] ?? true) === false) {
                return ['success' => false, 'status' => 'pending', 'message' => (string) ($response['error'] ?? '')];
            }

            $rawStatus = $this->extractStatus($response);

            $mapped = match (strtolower($rawStatus)) {
                'succeed', 'success', 'approved', 'accepted', 'done', 'completed', 'sent', 'delivered' => 'approved',
                'failed', 'rejected', 'error', 'canceled', 'cancelled', 'refused' => 'rejected',
                default => 'pending',
            };

            return [
                'success' => $mapped !== 'rejected',
                'status' => $mapped,
                'external_id' => $externalId,
                'data' => $response,
            ];

        } catch (\Throwable $e) {
            return ['success' => false, 'status' => 'pending', 'message' => $e->getMessage()];
        }
    }

    public function getInvoices(array $filters = []): array
    {
        try {
            $response = $this->soapCall('GetOutboxInvoiceList', [
                'query' => $filters,
            ]);

            if (($response['success'] ?? true) === false) {
                return ['success' => false, 'message' => (string) ($response['error'] ?? 'unknown')];
            }

            return ['success' => true, 'data' => $response];

        } catch (\Throwable $e) {
            return ['success' => false, 'message' => $e->getMessage()];
        }
    }

    public function getStatus(): array
    {
        $test = $this->test();

        return [
            'status' => ($test['success'] ?? false) ? 'active' : 'error',
            'message' => $test['message'] ?? '',
            'details' => null,
        ];
    }

    // ---- SOAP yardımcıları --------------------------------------------------

    /**
     * WS-Security UsernameToken auth header ile SOAP çağrısı yap.
     *
     * Auth her istekte header'da gider (oturum yok). callSoap SoapFault'u
     * fail-closed'a çevirir; credential/ham-yanıt loglanmaz.
     *
     * @param  string  $method  IIntegration operasyon adı
     * @param  array   $args    Operasyon argümanları
     * @return array             Normalize edilmiş yanıt (hata → success=false + error)
     */
    private function soapCall(string $method, array $args): array
    {
        $wsdl = trim((string) ($this->settings['wsdl_url'] ?? ''));

        return $this->callSoap($wsdl, $method, $args, [$this->securityHeader()]);
    }

    /**
     * WS-Security UsernameToken SoapHeader'ı üret. username/password modül
     * settings'ten gelir (encrypted cast'ten çözülmüş). PasswordText tipinde
     * (Uyumsoft düz metin UsernameToken bekler; imza/sertifika ayrı katman).
     */
    private function securityHeader(): \SoapHeader
    {
        $username = (string) ($this->settings['username'] ?? '');
        $password = (string) ($this->settings['password'] ?? '');

        // YALNIZ UsernameToken (iç içerik) — dış <wsse:Security> wrapper'ını
        // SoapHeader 'Security' adıyla ÜRETİR. Fragment'ı <wsse:Security> ile
        // sarıp SoapHeader'a vermek çift-Security (<Security><Security>) üretir
        // ve Uyumsoft mustUnderstand'ı dış boş Security'de görüp reddedebilir.
        $tokenXml = sprintf(
            '<wsse:UsernameToken xmlns:wsse="%s">'
            .'<wsse:Username>%s</wsse:Username>'
            .'<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">%s</wsse:Password>'
            .'</wsse:UsernameToken>',
            htmlspecialchars(self::WSSE_NS, ENT_QUOTES | ENT_XML1),
            htmlspecialchars($username, ENT_QUOTES | ENT_XML1),
            htmlspecialchars($password, ENT_QUOTES | ENT_XML1)
        );

        $var = new \SoapVar($tokenXml, XSD_ANYXML);

        // mustUnderstand=true (4. param) → tek <wsse:Security mustUnderstand="1">.
        return new \SoapHeader(self::WSSE_NS, 'Security', $var, true);
    }

    /**
     * SendInvoice argümanlarını kur. UBL-TR XML base64'lenip invoices[] içinde
     * Content + FileName ile iletilir (referans imzası).
     */
    private function buildSendInvoiceArgs(array $invoiceData, string $ublXml): array
    {
        $invoiceNumber = (string) ($invoiceData['invoice_number'] ?? 'invoice');

        return [
            'invoices' => [
                [
                    'FileName' => $invoiceNumber.'.xml',
                    'Content' => base64_encode($ublXml),
                ],
            ],
        ];
    }

    /**
     * Yanıttan fatura id/uuid çıkar (integrasyona göre değişen anahtar adları);
     * bulunamazsa fallback'e düş.
     */
    private function extractInvoiceId(array $response, string $fallback): string
    {
        foreach (['Id', 'id', 'InvoiceId', 'invoiceId', 'Uuid', 'uuid', 'Ettn', 'ettn'] as $key) {
            $found = $this->findFirst($response, $key);
            if ($found !== null && $found !== '') {
                return (string) $found;
            }
        }

        return $fallback;
    }

    /**
     * Yanıttan GİB/entegratör durum metnini çıkar.
     */
    private function extractStatus(array $response): string
    {
        foreach (['Status', 'status', 'GibStatus', 'gibStatus', 'State', 'state'] as $key) {
            $found = $this->findFirst($response, $key);
            if ($found !== null && $found !== '') {
                return (string) $found;
            }
        }

        return 'pending';
    }

    /**
     * İç içe normalize dizide belirli anahtarın ilk skaler değerini bul
     * (SOAP yanıt ağacı sarmalayıcı derinliği değişebildiği için).
     */
    private function findFirst(array $data, string $key)
    {
        if (array_key_exists($key, $data) && ! is_array($data[$key])) {
            return $data[$key];
        }

        foreach ($data as $value) {
            if (is_array($value)) {
                $found = $this->findFirst($value, $key);
                if ($found !== null) {
                    return $found;
                }
            }
        }

        return null;
    }

    /**
     * UblTrInvoiceBuilder'ın beklediği supplier dizisini modül settings'ten kur.
     */
    private function supplier(): array
    {
        return [
            'supplier_vkn' => (string) ($this->settings['supplier_vkn'] ?? ''),
            'supplier_title' => (string) ($this->settings['supplier_title'] ?? ''),
            'supplier_tax_office' => (string) ($this->settings['supplier_tax_office'] ?? ''),
            'supplier_address' => (string) ($this->settings['supplier_address'] ?? ''),
            'supplier_city' => (string) ($this->settings['supplier_city'] ?? ''),
        ];
    }
}
