<?php
declare(strict_types=1);

namespace App\Services;

use App\Core\Auth;
use App\Industry\Catalog;
use App\Models\Account;
use App\Models\Contract;
use App\Models\Invoice;
use App\Models\Order;
use App\Models\PortalDocument;
use App\Models\Quote;
use App\Models\ServiceJob;
use App\Models\Signature;
use App\Models\TenantCommerce;

final class CommerceService
{
    public const PAGES = [
        'invoices' => 'Invoices',
        'documents' => 'Documents',
        'sign' => 'Sign a paper',
        'orders' => 'My orders',
        'contract' => 'My contract / renewal',
        'service_request' => 'Service request',
        'equipment' => 'My equipment',
    ];

    public const SYSTEMS = [
        'orders' => 'Order system',
        'accounting' => 'Accounts / invoices',
        'service' => 'Service system',
    ];

    /** @return array<string, bool> */
    public static function defaultPages(string $industry): array
    {
        $pages = [
            'invoices' => true,
            'documents' => true,
            'sign' => true,
            'orders' => $industry === Catalog::DISTRIBUTION,
            'contract' => in_array($industry, [Catalog::SOFTWARE, Catalog::CONTRACT], true),
            'service_request' => $industry === Catalog::SERVICE,
            'equipment' => $industry === Catalog::SERVICE,
        ];
        return $pages;
    }

    public static function settings(): array
    {
        return TenantCommerce::current();
    }

    public static function portalOn(): bool
    {
        return !empty(self::settings()['customer_portal_on']);
    }

    /** @return array<string, bool> */
    public static function tenantPages(): array
    {
        $row = self::settings();
        $saved = is_array($row['customer_pages'] ?? null) ? $row['customer_pages'] : [];
        if ($saved === []) {
            return self::defaultPages(Catalog::currentCode());
        }
        $out = [];
        foreach (self::PAGES as $key => $_) {
            $out[$key] = !empty($saved[$key]);
        }
        return $out;
    }

    /**
     * @param array<string, mixed>|null $override
     * @return array<string, bool>
     */
    public static function pagesForCustomer(?array $override, string $industry): array
    {
        $base = self::tenantPages();
        if ($override) {
            foreach (self::PAGES as $key => $_) {
                if (array_key_exists($key, $override)) {
                    $base[$key] = !empty($override[$key]);
                }
            }
        }
        $industryDefault = self::defaultPages($industry);
        foreach ($base as $key => $on) {
            if ($on && empty($industryDefault[$key]) && !self::explicitlyOn($override, $key)) {
                // Tenant may still turn a page on for every customer.
            }
        }
        return $base;
    }

    /** @param array<string, mixed>|null $override */
    private static function explicitlyOn(?array $override, string $key): bool
    {
        return is_array($override) && !empty($override[$key]);
    }

    public static function systemOn(string $key): bool
    {
        $systems = self::settings()['systems'] ?? [];
        return ($systems[$key] ?? '') === 'connected';
    }

    public static function tempNumber(): string
    {
        return 'tmp-' . bin2hex(random_bytes(4));
    }

    public static function assignNumber(string $table, int $id, string $prefix): string
    {
        $number = $prefix . str_pad((string) $id, 4, '0', STR_PAD_LEFT);
        \App\Core\Database::instance()->query(
            "UPDATE `{$table}` SET number = :n WHERE id = :id AND tenant_id = :tid",
            ['n' => $number, 'id' => $id, 'tid' => Auth::instance()->tenantId()]
        );
        return $number;
    }

    public static function accountLabel(int $accountId): string
    {
        $a = Account::find($accountId);
        return $a ? (string) $a['name'] : 'Customer';
    }

    /**
     * Turn a won deal into the matching live record without retyping the customer.
     *
     * @return list<string>
     */
    public static function fromWonDeal(array $opp): array
    {
        $accountId = (int) ($opp['account_id'] ?? 0);
        $oppId = (int) ($opp['id'] ?? 0);
        $ownerId = (int) ($opp['owner_id'] ?? Auth::instance()->id());
        $title = trim((string) ($opp['name'] ?? 'Won deal'));
        $amount = (float) ($opp['amount'] ?? 0);
        if ($accountId < 1 || $oppId < 1) {
            return [];
        }
        $made = [];
        $industry = Catalog::currentCode();

        if (!Quote::forOpportunity($oppId)) {
            $qid = Quote::create([
                'account_id' => $accountId,
                'opportunity_id' => $oppId,
                'owner_id' => $ownerId,
                'number' => self::tempNumber(),
                'title' => $title,
                'amount' => $amount,
                'status' => 'draft',
                'body' => 'Created from won ' . strtolower(opportunity_label()) . ' without retyping the customer.',
            ]);
            self::assignNumber('quotes', $qid, 'Q-');
            $made[] = 'quote';
        }

        if ($industry === Catalog::DISTRIBUTION && !Order::forOpportunity($oppId)) {
            $oid = Order::create([
                'account_id' => $accountId,
                'opportunity_id' => $oppId,
                'owner_id' => $ownerId,
                'number' => self::tempNumber(),
                'title' => $title,
                'amount' => $amount,
                'status' => 'open',
                'ordered_at' => date('Y-m-d'),
            ]);
            self::assignNumber('orders', $oid, 'SO-');
            $made[] = 'order';
        }

        if (in_array($industry, [Catalog::SOFTWARE, Catalog::CONTRACT], true) && !Contract::forOpportunity($oppId)) {
            $cid = Contract::create([
                'account_id' => $accountId,
                'opportunity_id' => $oppId,
                'owner_id' => $ownerId,
                'number' => self::tempNumber(),
                'title' => $title,
                'amount' => $amount,
                'start_date' => date('Y-m-d'),
                'end_date' => date('Y-m-d', strtotime('+1 year')),
                'renewal_date' => date('Y-m-d', strtotime('+1 year')),
                'status' => 'active',
            ]);
            self::assignNumber('contracts', $cid, 'CT-');
            $made[] = 'contract';
        }

        if ($industry === Catalog::SERVICE && !ServiceJob::forOpportunity($oppId)) {
            $jid = ServiceJob::create([
                'account_id' => $accountId,
                'opportunity_id' => $oppId,
                'assignee_id' => $ownerId,
                'number' => self::tempNumber(),
                'subject' => $title,
                'body' => 'Opened from a won deal.',
                'status' => 'open',
                'source' => 'deal_won',
            ]);
            self::assignNumber('service_jobs', $jid, 'JOB-');
            $made[] = 'job';
        }

        return $made;
    }

    public static function shareQuote(array $quote): int
    {
        self::storeReplacement('quote', (int) $quote['id']);
        $keep = self::keepOneRelated('quote', (int) $quote['id']);
        if ($keep) {
            PortalDocument::update((int) $keep['id'], [
                'shared' => 1,
                'title' => (string) $quote['title'],
                'body' => (string) ($quote['body'] ?? ''),
            ]);
            $id = (int) $keep['id'];
        } else {
            $id = PortalDocument::create([
                'account_id' => (int) $quote['account_id'],
                'owner_id' => (int) ($quote['owner_id'] ?? Auth::instance()->id()),
                'related_type' => 'quote',
                'related_id' => (int) $quote['id'],
                'title' => (string) $quote['title'],
                'kind' => 'quote',
                'body' => (string) ($quote['body'] ?? ('Quote ' . $quote['number'])),
                'shared' => 1,
            ]);
        }
        self::attachUpload($id);
        self::attachRelatedFile($id, 'quote', (int) $quote['id']);
        return $id;
    }

    public static function shareContract(array $contract): int
    {
        $keep = self::keepOneRelated('contract', (int) $contract['id']);
        $title = (string) $contract['title'];
        $body = 'Contract ' . (string) $contract['number'];
        if ($keep) {
            PortalDocument::update((int) $keep['id'], [
                'shared' => 1,
                'title' => $title,
                'body' => $body,
            ]);
            $id = (int) $keep['id'];
        } else {
            $id = PortalDocument::create([
                'account_id' => (int) $contract['account_id'],
                'owner_id' => (int) ($contract['owner_id'] ?? Auth::instance()->id()),
                'related_type' => 'contract',
                'related_id' => (int) $contract['id'],
                'title' => $title,
                'kind' => 'contract',
                'body' => $body,
                'shared' => 1,
            ]);
        }
        self::attachRelatedFile($id, 'contract', (int) $contract['id']);
        return $id;
    }

    public static function requestSignature(int $accountId, string $title, string $body, string $relatedType, int $relatedId): int
    {
        if ($relatedType === 'quote' || $relatedType === 'contract') {
            self::storeReplacement($relatedType, $relatedId);
        }
        $keep = self::keepOneRelated($relatedType, $relatedId);
        $sig = $keep ? Signature::forDocument((int) $keep['id']) : null;
        $reuse = $keep && (!$sig || ($sig['status'] ?? '') !== 'signed');
        if ($reuse) {
            $docId = (int) $keep['id'];
            PortalDocument::update($docId, [
                'shared' => 1,
                'title' => $title,
                'body' => $body,
            ]);
            if (!$sig) {
                Signature::create([
                    'document_id' => $docId,
                    'account_id' => $accountId,
                    'status' => 'pending',
                ]);
            } elseif (($sig['status'] ?? '') !== 'pending') {
                Signature::update((int) $sig['id'], [
                    'status' => 'pending',
                    'signer_name' => null,
                    'signed_at' => null,
                    'signed_file_name' => null,
                    'note' => null,
                ]);
            }
        } else {
            $docId = PortalDocument::create([
                'account_id' => $accountId,
                'owner_id' => (int) Auth::instance()->id(),
                'related_type' => $relatedType,
                'related_id' => $relatedId,
                'title' => $title,
                'kind' => 'esign',
                'body' => $body,
                'shared' => 1,
            ]);
            Signature::create([
                'document_id' => $docId,
                'account_id' => $accountId,
                'status' => 'pending',
            ]);
        }
        self::attachUpload($docId);
        self::attachRelatedFile($docId, $relatedType, $relatedId);
        return $docId;
    }

    public static function storeReplacement(string $entity, int $id): void
    {
        $file = $_FILES['pdf'] ?? null;
        if (!is_array($file) || (int) ($file['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE) {
            return;
        }
        $name = DocumentStore::accept($file);
        DocumentStore::saveEntity($entity, $id, (string) $file['tmp_name']);
        if ($entity === 'quote') {
            Quote::update($id, ['file_name' => $name]);
        }
        if ($entity === 'contract') {
            Contract::update($id, ['file_name' => $name]);
        }
        if ($entity === 'invoice') {
            Invoice::update($id, ['file_name' => $name]);
        }
    }

    /** Hide extra unsigned copies so the customer sees one paper. */
    public static function keepOneRelated(string $relatedType, int $relatedId): ?array
    {
        $docs = PortalDocument::allByRelated($relatedType, $relatedId);
        if ($docs === []) {
            return null;
        }
        $keep = null;
        foreach ($docs as $d) {
            $sig = Signature::forDocument((int) $d['id']);
            if ($sig && ($sig['status'] ?? '') === 'pending') {
                $keep = $d;
                break;
            }
        }
        $keep ??= $docs[0];
        foreach ($docs as $d) {
            if ((int) $d['id'] === (int) $keep['id']) {
                continue;
            }
            $sig = Signature::forDocument((int) $d['id']);
            if ($sig && ($sig['status'] ?? '') === 'pending') {
                Signature::update((int) $sig['id'], [
                    'status' => 'declined',
                    'note' => 'Replaced by a newer paper.',
                ]);
            }
            if (!$sig || ($sig['status'] ?? '') !== 'signed') {
                PortalDocument::update((int) $d['id'], ['shared' => 0]);
            }
        }
        return $keep;
    }

    /** @param array<string, mixed>|null $file */
    public static function attachUpload(int $documentId, ?array $file = null): void
    {
        $file ??= $_FILES['pdf'] ?? null;
        if (!is_array($file) || (int) ($file['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE) {
            return;
        }
        $name = DocumentStore::accept($file);
        DocumentStore::save($documentId, (string) $file['tmp_name']);
        PortalDocument::update($documentId, ['file_name' => $name]);
    }

    public static function attachRelatedFile(int $documentId, string $relatedType, int $relatedId): void
    {
        $doc = PortalDocument::find($documentId);
        if ($doc && !empty($doc['file_name'])) {
            return;
        }
        $entity = $relatedType === 'quote' || $relatedType === 'contract' ? $relatedType : '';
        if ($entity === '') {
            return;
        }
        $row = $entity === 'quote' ? Quote::find($relatedId) : Contract::find($relatedId);
        if (!$row || empty($row['file_name'])) {
            return;
        }
        $tid = (int) Auth::instance()->tenantId();
        $src = DocumentStore::entityPath($tid, $entity, $relatedId);
        if (!is_file($src)) {
            return;
        }
        DocumentStore::copyToDocument($documentId, $src);
        PortalDocument::update($documentId, ['file_name' => (string) $row['file_name']]);
    }

    public static function invoiceFromOrder(array $order, string $fileName): int
    {
        $id = Invoice::create([
            'account_id' => (int) $order['account_id'],
            'order_id' => (int) $order['id'],
            'owner_id' => (int) ($order['owner_id'] ?? Auth::instance()->id()),
            'number' => self::tempNumber(),
            'title' => (string) $order['title'],
            'amount' => (float) $order['amount'],
            'paid_amount' => 0,
            'status' => 'unpaid',
            'file_name' => $fileName,
            'due_date' => date('Y-m-d', strtotime('+14 days')),
        ]);
        self::assignNumber('invoices', $id, 'INV-');
        return $id;
    }
}
