<?php

namespace WPSN\Modules\Licensing;

defined('ABSPATH') || exit;

/**
 * Normalizes billing/account data for the wp-admin Billing Center view.
 *
 * The remote SaaS payload is intentionally flexible: if the license verifier
 * returns extra billing metadata (plan, seats, invoices, portal URLs, etc.) we
 * surface it in wp-admin. Site owners can also provide manual fallback URLs in
 * plugin settings.
 */
final class BillingCenterData
{
    /**
     * @param array<string,mixed>|null $cache
     * @param array<string,mixed> $settings
     * @param array<string,mixed>|null $billingCache
     * @return array<string,mixed>
     */
    public static function build(?array $cache, array $settings, ?array $billingCache = null): array
    {
        $license = [];
        if (is_array($cache) && isset($cache['license']) && is_array($cache['license'])) {
            $license = $cache['license'];
        }

        $licenseBilling = [];
        if (isset($license['billing']) && is_array($license['billing'])) {
            $licenseBilling = $license['billing'];
        }

        $billingSync = [];
        if (is_array($billingCache) && isset($billingCache['billing']) && is_array($billingCache['billing'])) {
            $billingSync = $billingCache['billing'];
        }

        $billing = [];
        if (!empty($licenseBilling) || !empty($billingSync)) {
            $billing = array_replace_recursive($licenseBilling, $billingSync);
        }

        $lic = isset($settings['licensing']) && is_array($settings['licensing']) ? $settings['licensing'] : [];

        $domains = self::normalizeDomains(
            $billing['domains']
                ?? $billing['site_assignments']
                ?? $billing['sites']
                ?? $license['domains']
                ?? $license['sites']
                ?? []
        );

        $seatSummary = self::extractUsageSummary(
            $billing['usage']['seats'] ?? ($billing['seats'] ?? null),
            $billing,
            $license,
            ['seat_used', 'seats_used', 'used_seats', 'used'],
            ['seat_limit', 'seats_limit', 'max_seats', 'limit']
        );

        $siteSummary = self::extractUsageSummary(
            $billing['usage']['sites'] ?? ($billing['sites_summary'] ?? $billing['sites_usage'] ?? null),
            $billing,
            $license,
            ['site_count', 'sites_count', 'domain_count', 'domains_count', 'used'],
            ['site_limit', 'sites_limit', 'domain_limit', 'domains_limit', 'limit']
        );

        if ($siteSummary['used'] === null && !empty($domains)) {
            $siteSummary['used'] = count($domains);
        }

        $actions = [
            'portal_url' => self::pickUrl($billing, ['portal_url', 'customer_portal_url', 'manage_url'], $lic, ['billing_portal_url']),
            'account_url' => self::pickUrl($billing, ['account_url', 'account_portal_url'], $lic, ['account_portal_url']),
            'change_plan_url' => self::pickUrl($billing, ['change_plan_url', 'plan_url', 'upgrade_url'], $lic, ['change_plan_url', 'purchase_url']),
            'cancel_url' => self::pickUrl($billing, ['cancel_url', 'cancel_subscription_url'], $lic, ['cancel_url']),
            'payment_method_url' => self::pickUrl($billing, ['payment_method_url', 'billing_details_url'], $lic, ['payment_method_url']),
            'invoices_url' => self::pickUrl($billing, ['invoices_url', 'invoice_list_url'], $lic, ['invoices_url']),
            'support_url' => self::pickUrl($billing, ['support_url', 'billing_support_url'], $lic, ['billing_support_url']),
            'purchase_url' => self::pickUrl($billing, ['purchase_url', 'upgrade_url'], $lic, ['purchase_url']),
        ];

        $embed = self::buildEmbedConfig($actions, $lic);

        $source = 'manual';
        if (!empty($billingSync)) {
            $source = 'billing_sync';
        } elseif (!empty($licenseBilling)) {
            $source = 'license_payload';
        }

        return [
            'status' => self::firstString([$license, $cache ?? []], ['status'], 'unknown'),
            'plan' => self::firstString([$billingSync, $billing, $license], ['plan', 'tier', 'edition'], ''),
            'expires_at' => self::firstString([$billingSync, $billing, $license], ['expires_at', 'renews_at', 'next_billing_at', 'current_period_end'], ''),
            'customer_email' => self::firstString([$billingSync, $billing, $license], ['customer_email', 'email'], ''),
            'customer_name' => self::firstString([$billingSync, $billing, $license], ['customer_name', 'name'], ''),
            'subscription_id' => self::firstString([$billingSync, $billing, $license], ['subscription_id', 'subscription', 'id'], ''),
            'seat_used' => $seatSummary['used'],
            'seat_limit' => $seatSummary['limit'],
            'site_count' => $siteSummary['used'],
            'site_limit' => $siteSummary['limit'],
            'domains' => $domains,
            'actions' => $actions,
            'invoices' => self::normalizeInvoices($billing['invoices'] ?? $license['invoices'] ?? []),
            'embed' => $embed,
            'source' => $source,
            'sync_checked_at' => is_array($billingCache) && isset($billingCache['checked_at']) ? (int) $billingCache['checked_at'] : 0,
            'sync_last_error' => is_array($billingCache) && isset($billingCache['last_error']) ? (string) $billingCache['last_error'] : '',
            'sync_trusted' => is_array($billingCache) && !empty($billingCache['trusted']),
            'sync_status' => is_array($billingCache) && isset($billingCache['status']) ? (string) $billingCache['status'] : '',
            'has_data' => !empty($domains)
                || !empty($actions['portal_url'])
                || !empty($actions['account_url'])
                || !empty($actions['invoices_url'])
                || !empty($actions['purchase_url'])
                || !empty($actions['support_url'])
                || !empty($actions['change_plan_url'])
                || !empty($actions['cancel_url'])
                || !empty($actions['payment_method_url'])
                || self::firstString([$billingSync, $billing, $license], ['plan', 'tier', 'edition'], '') !== ''
                || self::firstString([$billingSync, $billing, $license], ['subscription_id', 'subscription', 'id'], '') !== ''
                || !empty($billing['invoices'])
                || !empty($license['invoices'])
                || !empty($billingSync),
        ];
    }

    /**
     * @param array<string,mixed> $actions
     * @param array<string,mixed> $lic
     * @return array<string,mixed>
     */
    private static function buildEmbedConfig(array $actions, array $lic): array
    {
        $embedEnabled = !empty($lic['billing_embed']);
        $height = isset($lic['billing_embed_height']) ? (int) $lic['billing_embed_height'] : 980;
        $height = max(480, min(2000, $height));

        $url = '';
        if (!empty($actions['portal_url'])) {
            $url = (string) $actions['portal_url'];
        } elseif (!empty($actions['account_url'])) {
            $url = (string) $actions['account_url'];
        }

        $allowedHosts = self::normalizeHostList($lic['billing_embed_allowed_hosts'] ?? []);
        $serverHost = self::normalizeHost((string) ($lic['server_url'] ?? ''));
        if ($serverHost !== '' && !in_array($serverHost, $allowedHosts, true)) {
            $allowedHosts[] = $serverHost;
        }

        $manualUrls = array_values(array_filter([
            isset($lic['billing_portal_url']) ? (string) $lic['billing_portal_url'] : '',
            isset($lic['account_portal_url']) ? (string) $lic['account_portal_url'] : '',
        ]));

        $host = self::normalizeHost($url);
        $manual = $url !== '' && in_array($url, $manualUrls, true);

        $canEmbed = false;
        $reason = '';
        if (!$embedEnabled) {
            $reason = 'disabled';
        } elseif ($url === '') {
            $reason = 'missing_url';
        } elseif ($host === '') {
            $reason = 'invalid_url';
        } elseif ($manual || in_array($host, $allowedHosts, true)) {
            $canEmbed = true;
        } else {
            $reason = 'host_not_allowlisted';
        }

        return [
            'enabled' => $embedEnabled,
            'can_embed' => $canEmbed,
            'reason' => $reason,
            'url' => $url,
            'host' => $host,
            'height' => $height,
            'allowed_hosts' => $allowedHosts,
        ];
    }

    /**
     * @param array<int|string,mixed>|mixed $nested
     * @param array<string,mixed> $billing
     * @param array<string,mixed> $license
     * @param array<int,string> $usedKeys
     * @param array<int,string> $limitKeys
     * @return array{used: int|null, limit: int|null}
     */
    private static function extractUsageSummary($nested, array $billing, array $license, array $usedKeys, array $limitKeys): array
    {
        $used = null;
        $limit = null;

        if (is_array($nested)) {
            $used = self::firstInt([$nested], $usedKeys, null);
            $limit = self::firstInt([$nested], $limitKeys, null);
        }

        if ($used === null) {
            $used = self::firstInt([$billing, $license], $usedKeys, null);
        }
        if ($limit === null) {
            $limit = self::firstInt([$billing, $license], $limitKeys, null);
        }

        return ['used' => $used, 'limit' => $limit];
    }

    /**
     * @param array<string,mixed> $remote
     * @param array<int,string> $remoteKeys
     * @param array<string,mixed> $settings
     * @param array<int,string> $settingKeys
     */
    private static function pickUrl(array $remote, array $remoteKeys, array $settings, array $settingKeys): string
    {
        $url = self::firstString([$remote], $remoteKeys, '');
        if ($url !== '') {
            return self::normalizeUrl($url);
        }

        $url = self::firstString([$settings], $settingKeys, '');
        return self::normalizeUrl($url);
    }

    private static function normalizeUrl(string $url): string
    {
        $url = trim($url);
        if ($url === '') {
            return '';
        }

        if (strpos($url, '/') === 0 && strpos($url, '//') !== 0) {
            return home_url($url);
        }

        return $url;
    }

    /**
     * @param array<int|string,mixed> $value
     * @return array<int,string>
     */
    private static function normalizeDomains($value): array
    {
        $domains = [];

        if (is_string($value)) {
            $value = preg_split('/[\r\n,]+/', $value) ?: [];
        }

        if (!is_array($value)) {
            return [];
        }

        foreach ($value as $row) {
            $domain = '';
            if (is_string($row)) {
                $domain = trim($row);
                if ($domain !== '' && strpos($domain, '://') !== false) {
                    $host = wp_parse_url($domain, PHP_URL_HOST);
                    if (is_string($host) && $host !== '') {
                        $domain = $host;
                    }
                }
            } elseif (is_array($row)) {
                foreach (['domain', 'host', 'site', 'site_url', 'name', 'url'] as $key) {
                    if (empty($row[$key])) {
                        continue;
                    }
                    $candidate = trim((string) $row[$key]);
                    if ($candidate === '') {
                        continue;
                    }
                    if (strpos($candidate, '://') !== false) {
                        $host = wp_parse_url($candidate, PHP_URL_HOST);
                        if (is_string($host) && $host !== '') {
                            $candidate = $host;
                        }
                    }
                    $domain = $candidate;
                    break;
                }
            }

            $domain = strtolower(trim($domain));
            if ($domain === '') {
                continue;
            }
            $domains[] = $domain;
        }

        return array_values(array_unique($domains));
    }

    /**
     * @param array<int|string,mixed> $value
     * @return array<int,array<string,string>>
     */
    private static function normalizeInvoices($value): array
    {
        if (!is_array($value)) {
            return [];
        }

        $out = [];
        foreach ($value as $row) {
            if (!is_array($row)) {
                continue;
            }

            $amount = self::firstString([$row], ['amount_label', 'total_label', 'formatted_total', 'formatted_amount'], '');
            if ($amount === '') {
                $currency = strtoupper(self::firstString([$row], ['currency'], ''));
                $number = self::firstString([$row], ['amount', 'total'], '');
                if ($number !== '') {
                    $amount = trim($currency . ' ' . $number);
                }
            }

            $downloadUrl = self::pickUrl($row, ['download_url', 'pdf_url', 'invoice_url', 'url'], [], []);
            $item = [
                'id' => self::firstString([$row], ['id', 'number', 'invoice_number'], ''),
                'number' => self::firstString([$row], ['number', 'invoice_number', 'id'], ''),
                'date' => self::firstString([$row], ['date', 'issued_at', 'created_at', 'paid_at'], ''),
                'status' => self::firstString([$row], ['status'], ''),
                'amount' => $amount,
                'download_url' => $downloadUrl,
            ];

            if ($item['id'] === '' && $item['number'] === '') {
                continue;
            }
            $out[] = $item;
        }

        return $out;
    }

    /**
     * @param array<int|string,mixed> $value
     * @return array<int,string>
     */
    private static function normalizeHostList($value): array
    {
        if (is_string($value)) {
            $value = preg_split('/[\r\n,]+/', $value) ?: [];
        }

        if (!is_array($value)) {
            return [];
        }

        $hosts = [];
        foreach ($value as $row) {
            if (!is_string($row)) {
                continue;
            }
            $host = self::normalizeHost($row);
            if ($host !== '') {
                $hosts[] = $host;
            }
        }

        return array_values(array_unique($hosts));
    }

    private static function normalizeHost(string $value): string
    {
        $value = trim($value);
        if ($value === '') {
            return '';
        }

        $host = wp_parse_url($value, PHP_URL_HOST);
        if (!is_string($host) || $host === '') {
            if (preg_match('/^[A-Za-z0-9.-]+$/', $value)) {
                $host = $value;
            } else {
                return '';
            }
        }

        return strtolower($host);
    }

    /**
     * @param array<int,array<string,mixed>> $sources
     * @param array<int,string> $keys
     */
    private static function firstString(array $sources, array $keys, string $default): string
    {
        foreach ($sources as $source) {
            if (!is_array($source)) {
                continue;
            }
            foreach ($keys as $key) {
                if (!isset($source[$key]) || !is_scalar($source[$key])) {
                    continue;
                }
                $value = trim((string) $source[$key]);
                if ($value !== '') {
                    return $value;
                }
            }
        }

        return $default;
    }

    /**
     * @param array<int,array<string,mixed>> $sources
     * @param array<int,string> $keys
     */
    private static function firstInt(array $sources, array $keys, ?int $default): ?int
    {
        foreach ($sources as $source) {
            if (!is_array($source)) {
                continue;
            }
            foreach ($keys as $key) {
                if (!isset($source[$key])) {
                    continue;
                }
                $value = $source[$key];
                if (is_numeric($value)) {
                    return (int) $value;
                }
            }
        }

        return $default;
    }
}
