<?php
/**
 * Operator Dashboard (CF12 - client side).
 *
 * Per-operator dashboard with 6 metric tiles + recent activity feed.
 *
 * Per _UX-SPEC.md §2.2:
 *   - 6 metric tiles: balance, backlinks offered, backlinks received,
 *     approvals, placements verified, 24h clicks+impressions
 *   - Recent activity feed (last 10 events)
 *   - Latest credit transactions (last 5)
 *
 * All render paths go through Admin\Renderer (CF14 build-for-change rule).
 *
 * @package SwapAds\Client\Admin
 * @since   1.1.0
 */

declare(strict_types=1);

namespace SwapAds\Client\Admin;

use SwapAds\Client\Admin\ClientErrorRenderer;
use SwapAds\Client\Api\RestClient;
use SwapAds\Client\License\LicenseManager;

/**
 * Class OperatorDashboardPage.
 *
 * @since 1.1.0
 */
final class OperatorDashboardPage
{
    public const MENU_SLUG    = 'swapads-client-dashboard';
    public const CACHE_TTL    = 60; // seconds, dashboard data freshness tradeoff

    /**
     * Register WP hooks (admin menu).
     */
    public static function register(): void
    {
        add_action('admin_menu', [self::class, 'addMenu']);
    }

    /**
     * Register sub-menu page under swapads-client top level.
     */
    /**
     * Register the Operator Dashboard submenu.
     *
     * F201 (2026-07-30): the menu has moved to OperatorDashboardHubPage
     * (which is the FIRST submenu and has Balance + Setup tabs). This
     * `addMenu()` is now a no-op kept for back-compat with callers that
     * may still dispatch to MENU_SLUG.
     */
    public static function addMenu(): void
    {
        // intentionally empty — menu moved to OperatorDashboardHubPage (F201).
    }

    /**
     * Fetch dashboard data from server.
     *
     * F198: RestClient::request() never throws for REST/transport
     * failures — lastError() captures the normalized envelope and we
     * persist it so render() can show it inline.
     *
     * @return array<string, mixed>|null Null on error.
     */
    public const ERROR_CONTEXT = 'swapads_client_dashboard';

    public static function fetchDashboard(): ?array
    {
        $client = RestClient::fromOption();
        try {
            $response = $client->get('/v1/dashboard/operator');
        } catch (\Throwable $e) {
            set_transient(self::ERROR_CONTEXT, [
                'error_code'  => 'FATAL',
                'message'     => 'Unexpected fatal error: ' . $e->getMessage(),
                'http_status' => 0,
                'when'        => time(),
            ], MINUTE_IN_SECONDS);
            return null;
        }
        if (!is_array($response)) {
            if (($err = $client->lastError()) !== null) {
                set_transient(self::ERROR_CONTEXT, $err, MINUTE_IN_SECONDS);
            }
            return null;
        }
        if (empty($response['success'])) {
            $err = $client->lastError() ?? $response;
            set_transient(self::ERROR_CONTEXT, $err, MINUTE_IN_SECONDS);
            return null;
        }
        return $response;
    }

    /**
     * Render the operator dashboard.
     */
    public static function render(): void
    {
        if (!current_user_can('manage_options')) {
            wp_die('Insufficient permissions', 'Forbidden', ['response' => 403]);
        }

        echo Renderer::pageHeader(
            'Operator Dashboard',
            'Overview of your SwapAds activity: credits, backlinks, approvals, and recent events.'
        );

        if (!LicenseManager::isLicensed()) {
            echo Renderer::card(
                'License required',
                '<p>Activate your license to view the operator dashboard.</p>',
                'swapads-card-warning'
            );
            echo Renderer::pageFooter();
            return;
        }

        // F198: surface any persisted error from the previous load attempt.
        $persisted = get_transient(self::ERROR_CONTEXT);
        if (is_array($persisted)) {
            echo ClientErrorRenderer::render(self::ERROR_CONTEXT, $persisted, 'load operator dashboard');
            delete_transient(self::ERROR_CONTEXT);
        }

        $data = self::fetchDashboard();
        if ($data === null) {
            // F198: also surface the error from THIS attempt, in case the
            // transient above wasn't populated (cold page load).
            echo Renderer::card(
                'Dashboard unavailable',
                '<p>Could not fetch dashboard data. See the notice above for the exact server response.</p>',
                'swapads-card-error'
            );
            echo Renderer::pageFooter();
            return;
        }

        // 6 metric tiles
        echo '<div class="swapads-metric-grid">';
        // Per F-AD-CREDITS (2026-07-30): two credit tiles (backlinks + ads)
        echo self::renderMetricTile(
            'Backlink credits',
            (int) ($data['credits']['backlinks']['balance'] ?? 0),
            sprintf(
                'Earned %d / Spent %d',
                (int) ($data['credits']['backlinks']['lifetime_earned'] ?? 0),
                (int) ($data['credits']['backlinks']['lifetime_spent'] ?? 0)
            )
        );
        echo self::renderMetricTile(
            'Ad credits',
            (int) ($data['credits']['ads']['balance'] ?? 0),
            sprintf(
                'Earned %d / Spent %d (cap %d/day)',
                (int) ($data['credits']['ads']['lifetime_earned'] ?? 0),
                (int) ($data['credits']['ads']['lifetime_spent'] ?? 0),
                (int) ($data['credits']['ads']['daily_earn_cap'] ?? 10000)
            )
        );
        echo self::renderMetricTile('Backlinks offered',       self::formatPair($data['backlinks_offered'] ?? []), 'active / inactive / total');
        echo self::renderMetricTile('Backlinks received',      self::formatPair($data['backlinks_received'] ?? []),'active approvals');
        echo self::renderMetricTile('Approvals',               self::formatQuad($data['approvals'] ?? []),         'pending / approved / rejected / placed');
        echo self::renderMetricTile('Placements verified',     self::formatQuad($data['placements'] ?? []),        'verified / failed / pending');
        echo self::renderMetricTile('24h clicks / impressions', self::formatPair($data['activity_24h'] ?? []),    'last 24 hours');
        echo '</div>';

        // Quick actions
        echo Renderer::card(
            'Quick actions',
            self::renderQuickActions()
        );

        // Recent events
        $eventsHtml = self::renderRecentEvents($data['recent_events'] ?? []);
        echo Renderer::card('Recent activity (last 10)', $eventsHtml === '' ? '<p class="swapads-muted">No recent activity yet.</p>' : $eventsHtml);

        // Recent credit transactions
        $txHtml = self::renderRecentTransactions($data['recent_transactions'] ?? []);
        echo Renderer::card('Latest credit transactions (last 5)', $txHtml === '' ? '<p class="swapads-muted">No transactions yet.</p>' : $txHtml);

        echo self::renderGeneratedFooter($data);
        echo Renderer::pageFooter();
    }

    /**
     * Render one metric tile.
     */
    public static function renderMetricTile(string $label, $value, string $description = ''): string
    {
        if (is_int($value)) {
            $valueHtml = '<div class="swapads-metric-value">' . esc_html(number_format_i18n($value)) . '</div>';
        } else {
            $valueHtml = '<div class="swapads-metric-value">' . esc_html((string) $value) . '</div>';
        }
        $desc = $description !== '' ? '<div class="swapads-metric-desc">' . esc_html($description) . '</div>' : '';
        $html  = '<div class="swapads-metric-tile">';
        $html .= '<div class="swapads-metric-label">' . esc_html($label) . '</div>';
        $html .= $valueHtml;
        $html .= $desc;
        $html .= '</div>';
        return $html;
    }

    /**
     * Format "X active / Y total" pair.
     */
    public static function formatPair(array $row): string
    {
        $a = (int) ($row['active'] ?? 0);
        $i = (int) ($row['inactive'] ?? 0);
        $t = (int) ($row['total'] ?? 0);
        return sprintf('%d active / %d total', $a, $t > 0 ? $t : ($a + $i));
    }

    /**
     * Format "X verified / Y failed / Z pending" quad.
     */
    public static function formatQuad(array $row): string
    {
        $v = (int) ($row['verified'] ?? 0);
        $f = (int) ($row['failed'] ?? 0);
        $p = (int) ($row['pending'] ?? 0);
        return sprintf('%d verified / %d failed / %d pending', $v, $f, $p);
    }

    /**
     * Render quick action buttons.
     */
    public static function renderQuickActions(): string
    {
        $actions = [
            ['label' => 'Create Backlink',         'url' => admin_url('admin.php?page=swapads-client-backlinks')],
            ['label' => 'Review Suggestions',      'url' => admin_url('admin.php?page=swapads-client-approval')],
            ['label' => 'Edit Site Settings',      'url' => admin_url('admin.php?page=swapads-client')],
        ];
        $html = '<p>';
        foreach ($actions as $a) {
            $html .= '<a class="button" href="' . esc_url($a['url']) . '">' . esc_html($a['label']) . '</a> ';
        }
        $html .= '</p>';
        return $html;
    }

    /**
     * Render recent events (mixed kinds: approval / credit / placement).
     *
     * @param array<int, array<string, mixed>> $events
     */
    public static function renderRecentEvents(array $events): string
    {
        if (count($events) === 0) {
            return '';
        }
        $html = '<table class="widefat striped">';
        $html .= '<thead><tr><th>When</th><th>Kind</th><th>Details</th></tr></thead>';
        $html .= '<tbody>';
        foreach ($events as $e) {
            $when  = (string) ($e['when'] ?? '');
            $kind  = (string) ($e['kind'] ?? '');
            $details = self::formatEventDetails($e);
            $html .= '<tr>';
            $html .= '<td>' . esc_html($when) . '</td>';
            $html .= '<td><code>' . esc_html($kind) . '</code></td>';
            $html .= '<td>' . $details . '</td>'; // details already escaped
            $html .= '</tr>';
        }
        $html .= '</tbody></table>';
        return $html;
    }

    /**
     * Format a single event into an HTML cell.
     *
     * @param array<string, mixed> $e
     */
    public static function formatEventDetails(array $e): string
    {
        $kind = (string) ($e['kind'] ?? '');
        switch ($kind) {
            case 'approval':
                $id = (int) ($e['id'] ?? 0);
                $bid = (int) ($e['backlink_id'] ?? 0);
                $status = (string) ($e['status'] ?? '');
                return sprintf('#%d backlink #%d status changed to %s', $id, $bid, esc_html($status));
            case 'credit':
                $id = (int) ($e['id'] ?? 0);
                $delta = (int) ($e['delta'] ?? 0);
                $action = (string) ($e['action'] ?? '');
                $balance = (int) ($e['balance_after'] ?? 0);
                $siteUrl = $e['site_url'] ?? null;
                $domain = $siteUrl ? esc_html(parse_url($siteUrl, PHP_URL_HOST) ?: $siteUrl) : '';
                return sprintf('#%d %s %+d credits (balance now %d)%s', $id, esc_html($action), $delta, $balance, $domain ? " — {$domain}" : '');
            case 'placement':
                $id = (int) ($e['id'] ?? 0);
                $status = (string) ($e['status'] ?? '');
                return sprintf('#%d verification status: %s', $id, esc_html($status));
            default:
                return '<code>' . esc_html(wp_json_encode($e)) . '</code>';
        }
    }

    /**
     * Render the recent credit transactions card.
     *
     * @param array<int, array<string, mixed>> $txns
     */
    public static function renderRecentTransactions(array $txns): string
    {
        if (count($txns) === 0) {
            return '';
        }
        $html = '<table class="widefat striped">';
        $html .= '<thead><tr><th>When</th><th>Action</th><th>Delta</th><th>Balance after</th><th>Domain</th><th>Reason</th></tr></thead>';
        $html .= '<tbody>';
        foreach ($txns as $t) {
            $when = (string) ($t['when'] ?? '');
            $action = (string) ($t['action'] ?? '');
            $delta  = (int) ($t['delta'] ?? 0);
            $balance = (int) ($t['balance_after'] ?? 0);
            $reason = (string) ($t['reason'] ?? '');
            $siteUrl = $t['site_url'] ?? null;
            $domainHtml = $siteUrl ? esc_html(parse_url($siteUrl, PHP_URL_HOST) ?: $siteUrl) : '<span class="swapads-muted">—</span>';
            $html .= '<tr>';
            $html .= '<td>' . esc_html($when) . '</td>';
            $html .= '<td><code>' . esc_html($action) . '</code></td>';
            $html .= '<td>' . esc_html(($delta >= 0 ? '+' : '') . $delta) . '</td>';
            $html .= '<td>' . esc_html((string) $balance) . '</td>';
            $html .= '<td>' . $domainHtml . '</td>';
            $html .= '<td>' . esc_html($reason) . '</td>';
            $html .= '</tr>';
        }
        $html .= '</tbody></table>';
        return $html;
    }

    /**
     * Render the small "Generated at" footer with refresh hint.
     *
     * @param array<string, mixed> $data
     */
    public static function renderGeneratedFooter(array $data): string
    {
        $when = (string) ($data['generated_at'] ?? '');
        if ($when === '') {
            return '';
        }
        return '<p class="swapads-muted">Data fetched at <code>' . esc_html($when) . '</code>. ' .
               '<a href="#" onclick="location.reload(); return false;">Refresh</a></p>';
    }
}
