<?php

namespace MintyPHP\Module\Helpdesk\Service;

/**
 * Domain list orchestrator.
 *
 * Owns the shared filter + enrichment flow used by both the Grid.js
 * data endpoint (`domains-data()`) and the export endpoint
 * (`domains/export()`). Keeps the two in lockstep — same filters
 * visible in the grid, same rows produced by the export.
 *
 * Input: parsed filter values (from gridParseFiltersFromSchemaFile).
 * Output: `rows` enriched with security-level + state variant,
 *         `total` matching the filtered set *before* paging.
 */
final class DomainListService
{
    private const STATE_VARIANT_MAP = [
        'Aktiv' => 'success',
        'In Vorbereitung' => 'warning',
    ];

    public function __construct(
        private readonly BcODataGateway $bc,
        private readonly DomainSecurityLevelService $securityLevels,
    ) {
    }

    /**
     * @api Called from domains-data and domains/export endpoints
     * @param array{
     *     search?: string,
     *     customer?: string,
     *     contract_type?: string,
     *     state?: string,
     *     administration?: string,
     *     security_level?: string,
     *     order?: string,
     *     dir?: string,
     *     limit?: int,
     *     offset?: int
     * } $filters
     *
     * @return array{rows: list<array<string,mixed>>, total: int}
     */
    public function query(int $tenantId, array $filters): array
    {
        $search = trim((string) ($filters['search'] ?? ''));
        $customer = trim((string) ($filters['customer'] ?? ''));
        $contractType = trim((string) ($filters['contract_type'] ?? ''));
        $state = $this->normalizeAll((string) ($filters['state'] ?? ''));
        $administration = $this->normalizeAll((string) ($filters['administration'] ?? ''));
        $securityLevel = $this->normalizeAll((string) ($filters['security_level'] ?? ''));
        $order = (string) ($filters['order'] ?? 'Customer_Name');
        $dir = strtolower((string) ($filters['dir'] ?? 'asc')) === 'desc' ? 'desc' : 'asc';
        $limit = max(0, (int) ($filters['limit'] ?? 0));
        $offset = max(0, (int) ($filters['offset'] ?? 0));

        try {
            $allDomains = $this->bc->listDomains();
        } catch (\Throwable) {
            return ['rows' => [], 'total' => 0];
        }

        $enriched = $this->enrichContracts($allDomains);

        $filtered = $this->applyFilters($enriched, $tenantId, [
            'search' => $search,
            'customer' => $customer,
            'contract_type' => $contractType,
            'state' => $state,
            'administration' => $administration,
            'security_level' => $securityLevel,
        ]);

        $total = count($filtered);

        usort($filtered, static function (array $a, array $b) use ($order, $dir): int {
            $va = (string) ($a[$order] ?? '');
            $vb = (string) ($b[$order] ?? '');
            $cmp = strnatcasecmp($va, $vb);
            return $dir === 'desc' ? -$cmp : $cmp;
        });

        if ($limit > 0) {
            $filtered = array_slice($filtered, $offset, $limit);
        } elseif ($offset > 0) {
            $filtered = array_slice($filtered, $offset);
        }

        $rows = $this->prepareRows($filtered, $tenantId);

        return ['rows' => $rows, 'total' => $total];
    }

    private function normalizeAll(string $value): string
    {
        $trimmed = trim($value);
        return $trimmed === 'all' ? '' : $trimmed;
    }

    /**
     * @param list<array<string,mixed>> $domains
     * @return list<array<string,mixed>>
     */
    private function enrichContracts(array $domains): array
    {
        $lookup = [];
        try {
            foreach ($this->bc->listDomainContractLines() as $line) {
                $dnsNo = trim((string) ($line['No'] ?? ''));
                if ($dnsNo !== '' && !isset($lookup[$dnsNo])) {
                    $lookup[$dnsNo] = [
                        'contract_type' => trim((string) ($line['PI_Header_Type'] ?? '')),
                        'contract_no' => trim((string) ($line['Header_No'] ?? '')),
                        'contract_description' => trim((string) ($line['PI_Header_Description'] ?? '')),
                    ];
                }
            }
        } catch (\Throwable) {
            // best-effort: proceed without contract enrichment
        }

        foreach ($domains as &$domain) {
            $dnsNo = trim((string) ($domain['No'] ?? ''));
            $contract = $lookup[$dnsNo] ?? null;
            $domain['contract_type'] = $contract['contract_type'] ?? '';
            $domain['contract_no'] = $contract['contract_no'] ?? '';
            $domain['contract_description'] = $contract['contract_description'] ?? '';
        }
        unset($domain);

        return $domains;
    }

    /**
     * @param list<array<string,mixed>> $domains
     * @param array{search: string, customer: string, contract_type: string, state: string, administration: string, security_level: string} $f
     * @return list<array<string,mixed>>
     */
    private function applyFilters(array $domains, int $tenantId, array $f): array
    {
        if ($f['search'] !== '') {
            $needle = mb_strtolower($f['search']);
            $domains = array_values(array_filter($domains, static function (array $d) use ($needle): bool {
                $hay = mb_strtolower(
                    ($d['Customer_No'] ?? '') . ' '
                    . ($d['Customer_Name'] ?? '') . ' '
                    . ($d['URL'] ?? '') . ' '
                    . ($d['No'] ?? '') . ' '
                    . ($d['contract_type'] ?? '')
                );
                return str_contains($hay, $needle);
            }));
        }

        if ($f['customer'] !== '') {
            $needle = mb_strtolower($f['customer']);
            $domains = array_values(array_filter($domains, static function (array $d) use ($needle): bool {
                $hay = mb_strtolower(($d['Customer_No'] ?? '') . ' ' . ($d['Customer_Name'] ?? ''));
                return str_contains($hay, $needle);
            }));
        }

        if ($f['contract_type'] !== '') {
            $needle = mb_strtolower($f['contract_type']);
            $domains = array_values(array_filter($domains, static fn (array $d): bool => str_contains(mb_strtolower((string) ($d['contract_type'] ?? '')), $needle)));
        }

        if ($f['state'] !== '') {
            $value = $f['state'];
            $domains = array_values(array_filter($domains, static fn (array $d): bool => trim((string) ($d['State'] ?? '')) === $value));
        }

        if ($f['administration'] !== '') {
            $value = $f['administration'];
            $domains = array_values(array_filter($domains, static fn (array $d): bool => trim((string) ($d['Administration'] ?? '')) === $value));
        }

        if ($f['security_level'] !== '' && $tenantId > 0) {
            $domainNos = array_values(array_filter(
                array_map(static fn (array $d): string => trim((string) ($d['No'] ?? '')), $domains),
                static fn (string $n): bool => $n !== ''
            ));
            $lookup = $domainNos !== [] ? $this->securityLevels->getAllLevelsForDomains($tenantId, $domainNos) : [];
            $value = $f['security_level'];
            $domains = array_values(array_filter($domains, static function (array $d) use ($value, $lookup): bool {
                $no = trim((string) ($d['No'] ?? ''));
                return ($lookup[$no] ?? 'normal') === $value;
            }));
        }

        return $domains;
    }

    /**
     * @param list<array<string,mixed>> $domains
     * @return list<array<string,mixed>>
     */
    private function prepareRows(array $domains, int $tenantId): array
    {
        $lookup = [];
        if ($tenantId > 0) {
            $domainNos = array_values(array_filter(
                array_map(static fn (array $d): string => trim((string) ($d['No'] ?? '')), $domains),
                static fn (string $n): bool => $n !== ''
            ));
            if ($domainNos !== []) {
                $lookup = $this->securityLevels->getAllDetailsForDomains($tenantId, $domainNos);
            }
        }

        $debitorBaseUrl = lurl('helpdesk/debitor/');

        $rows = [];
        foreach ($domains as $domain) {
            $customerNo = trim((string) ($domain['Customer_No'] ?? ''));
            $domainState = (string) ($domain['State'] ?? '');
            $domainNo = (string) ($domain['No'] ?? '');
            $details = $lookup[$domainNo] ?? null;
            $level = $details['level'] ?? 'normal';
            $note = $details['note'] ?? '';

            $rows[] = [
                'No' => $domainNo,
                'Customer_No' => $customerNo,
                'Customer_Name' => (string) ($domain['Customer_Name'] ?? ''),
                'URL' => (string) ($domain['URL'] ?? ''),
                'State' => $domainState,
                'state_variant' => self::STATE_VARIANT_MAP[$domainState] ?? 'neutral',
                'Administration' => (string) ($domain['Administration'] ?? ''),
                'contract_type' => (string) ($domain['contract_type'] ?? ''),
                'contract_no' => (string) ($domain['contract_no'] ?? ''),
                'debitor_url' => $customerNo !== '' ? $debitorBaseUrl . rawurlencode($customerNo) : '',
                'security_level' => $level,
                'security_level_variant' => DomainSecurityLevelService::getBadgeVariant($level),
                'security_level_note' => $note,
            ];
        }

        return $rows;
    }
}
