<?php

namespace WPSN\Modules\Bulk;

use WPSN\SEO\MetaKeys;

defined('ABSPATH') || exit;

final class BulkSessionReportRepository
{
    private const OPTION_PREFIX = 'wpsn_bulk_queue_session_report_';
    private const MAX_ENTRIES = 2000;

    /**
     * @return array<string,mixed>
     */
    public function get(string $sessionId): array
    {
        $sessionId = sanitize_key($sessionId);
        if ($sessionId === '') {
            return [];
        }

        $raw = get_option($this->optionName($sessionId), []);
        return is_array($raw) ? $raw : [];
    }

    /**
     * @param array<string,mixed> $meta
     */
    public function touch(string $sessionId, array $meta = []): void
    {
        $sessionId = sanitize_key($sessionId);
        if ($sessionId === '') {
            return;
        }

        $report = $this->get($sessionId);
        if (empty($report)) {
            $report = [
                'session_id' => $sessionId,
                'created_at' => time(),
                'updated_at' => time(),
                'meta' => [],
                'entries' => [],
            ];
        }

        $reportMeta = isset($report['meta']) && is_array($report['meta']) ? $report['meta'] : [];
        foreach ($meta as $key => $value) {
            $sanitized = $this->sanitizeMetaValue($value);
            if ($sanitized !== null || $value === null) {
                $reportMeta[(string) $key] = $sanitized;
            }
        }

        $report['meta'] = $reportMeta;
        $report['updated_at'] = time();
        update_option($this->optionName($sessionId), $report, false);
    }

    /**
     * @param array<string,mixed> $fields
     */
    public function recordGenerate(string $sessionId, array $fields): void
    {
        $this->updateEntry($sessionId, $fields, 'generate');
    }

    /**
     * @param array<string,mixed> $fields
     */
    public function recordApply(string $sessionId, array $fields): void
    {
        $this->updateEntry($sessionId, $fields, 'apply');
    }

    /**
     * @param array<string,mixed> $fields
     */
    public function recordFix(string $sessionId, array $fields): void
    {
        $this->updateEntry($sessionId, $fields, 'fix');
    }

    /**
     * @return array<string,mixed>
     */
    public function summary(string $sessionId): array
    {
        $report = $this->get($sessionId);
        $entries = isset($report['entries']) && is_array($report['entries']) ? array_values($report['entries']) : [];

        $summary = [
            'objects' => count($entries),
            'generated' => 0,
            'generate_failed' => 0,
            'generate_skipped' => 0,
            'applied' => 0,
            'apply_failed' => 0,
            'apply_no_change' => 0,
            'apply_skipped' => 0,
            'fix_applied' => 0,
            'fix_failed' => 0,
            'fix_skipped' => 0,
        ];

        foreach ($entries as $entry) {
            if (!is_array($entry)) {
                continue;
            }

            $generateStatus = (string) ($entry['generate_status'] ?? '');
            if ($generateStatus === 'generated') {
                $summary['generated']++;
            } elseif ($generateStatus === 'failed') {
                $summary['generate_failed']++;
            } elseif ($generateStatus !== '') {
                $summary['generate_skipped']++;
            }

            $applyStatus = (string) ($entry['apply_status'] ?? '');
            if ($applyStatus === 'applied') {
                $summary['applied']++;
            } elseif ($applyStatus === 'failed') {
                $summary['apply_failed']++;
            } elseif ($applyStatus === 'no_change') {
                $summary['apply_no_change']++;
            } elseif ($applyStatus !== '') {
                $summary['apply_skipped']++;
            }

            $fixStatus = (string) ($entry['fix_status'] ?? '');
            if ($fixStatus === 'applied') {
                $summary['fix_applied']++;
            } elseif ($fixStatus === 'failed') {
                $summary['fix_failed']++;
            } elseif ($fixStatus !== '') {
                $summary['fix_skipped']++;
            }
        }

        return $summary;
    }

    /**
     * @param array<string,mixed> $session
     * @return array<string,mixed>
     */
    public function exportData(string $sessionId, array $session = []): array
    {
        $report = $this->get($sessionId);
        $entries = isset($report['entries']) && is_array($report['entries']) ? array_values($report['entries']) : [];
        usort($entries, static function (array $a, array $b): int {
            return ((int) ($b['updated_at'] ?? 0)) <=> ((int) ($a['updated_at'] ?? 0));
        });

        return [
            'exported_at' => gmdate('c'),
            'session' => $session,
            'report' => [
                'session_id' => isset($report['session_id']) ? (string) $report['session_id'] : sanitize_key($sessionId),
                'created_at' => isset($report['created_at']) ? (int) $report['created_at'] : 0,
                'updated_at' => isset($report['updated_at']) ? (int) $report['updated_at'] : 0,
                'meta' => isset($report['meta']) && is_array($report['meta']) ? $report['meta'] : [],
                'summary' => $this->summary($sessionId),
                'entries' => $entries,
            ],
        ];
    }

    /**
     * @param array<string,mixed> $fields
     */
    private function updateEntry(string $sessionId, array $fields, string $kind): void
    {
        $sessionId = sanitize_key($sessionId);
        if ($sessionId === '') {
            return;
        }

        $objectType = isset($fields['object_type']) ? sanitize_key((string) $fields['object_type']) : 'post';
        $objectId = isset($fields['object_id']) ? (int) $fields['object_id'] : 0;
        if ($objectId <= 0 && isset($fields['post_id'])) {
            $objectType = 'post';
            $objectId = (int) $fields['post_id'];
        }
        if (!in_array($objectType, ['post', 'term'], true) || $objectId <= 0) {
            return;
        }

        $report = $this->get($sessionId);
        if (empty($report)) {
            $report = [
                'session_id' => $sessionId,
                'created_at' => time(),
                'updated_at' => time(),
                'meta' => [],
                'entries' => [],
            ];
        }

        $entries = isset($report['entries']) && is_array($report['entries']) ? $report['entries'] : [];
        $key = $objectType . ':' . $objectId;
        $existing = isset($entries[$key]) && is_array($entries[$key]) ? $entries[$key] : $this->baseEntry($objectType, $objectId, $fields);

        $existing['object_type'] = $objectType;
        $existing['object_id'] = $objectId;
        $existing['object_subtype'] = isset($fields['object_subtype']) ? sanitize_key((string) $fields['object_subtype']) : (string) ($existing['object_subtype'] ?? '');
        if ((string) ($existing['object_title'] ?? '') === '') {
            $existing['object_title'] = $this->resolveObjectTitle($objectType, $objectId);
        }
        if ((string) ($existing['object_url'] ?? '') === '') {
            $existing['object_url'] = $this->resolveObjectUrl($objectType, $objectId, (string) ($existing['object_subtype'] ?? ''));
        }

        if ($kind === 'generate') {
            $existing['generate_status'] = sanitize_key((string) ($fields['generate_status'] ?? 'generated'));
            $existing['generated_at'] = gmdate('c');
            $existing['suggestion_source'] = isset($fields['suggestion_source']) ? sanitize_key((string) $fields['suggestion_source']) : (string) ($existing['suggestion_source'] ?? '');
            if (array_key_exists('suggested_title', $fields)) {
                $existing['suggested_title'] = (string) $fields['suggested_title'];
            }
            if (array_key_exists('suggested_description', $fields)) {
                $existing['suggested_description'] = (string) $fields['suggested_description'];
            }
            if (isset($fields['generate_note'])) {
                $existing['generate_note'] = (string) $fields['generate_note'];
            }
        } elseif ($kind === 'apply') {
            $existing['apply_status'] = sanitize_key((string) ($fields['apply_status'] ?? 'applied'));
            $existing['applied_at'] = gmdate('c');
            foreach (['before_title', 'before_description', 'after_title', 'after_description'] as $metaKey) {
                if (array_key_exists($metaKey, $fields)) {
                    $existing[$metaKey] = (string) $fields[$metaKey];
                }
            }
            if (isset($fields['changed_fields']) && is_array($fields['changed_fields'])) {
                $existing['changed_fields'] = array_values(array_unique(array_map('sanitize_key', $fields['changed_fields'])));
            }
            if (isset($fields['apply_note'])) {
                $existing['apply_note'] = (string) $fields['apply_note'];
            }
        } elseif ($kind === 'fix') {
            $existing['fix_status'] = sanitize_key((string) ($fields['fix_status'] ?? 'applied'));
            $existing['fixed_at'] = gmdate('c');
            if (isset($fields['fix_action'])) {
                $existing['fix_action'] = sanitize_key((string) $fields['fix_action']);
            }
            foreach (['before_title', 'before_description', 'after_title', 'after_description'] as $metaKey) {
                if (array_key_exists($metaKey, $fields)) {
                    $existing[$metaKey] = (string) $fields[$metaKey];
                }
            }
            if (isset($fields['fix_note'])) {
                $existing['fix_note'] = (string) $fields['fix_note'];
            }
        }

        $existing['updated_at'] = time();
        $entries[$key] = $existing;
        if (count($entries) > self::MAX_ENTRIES) {
            uasort($entries, static function (array $a, array $b): int {
                return ((int) ($b['updated_at'] ?? 0)) <=> ((int) ($a['updated_at'] ?? 0));
            });
            $entries = array_slice($entries, 0, self::MAX_ENTRIES, true);
        }

        $report['entries'] = $entries;
        $report['updated_at'] = time();
        update_option($this->optionName($sessionId), $report, false);
    }

    /**
     * @param array<string,mixed> $fields
     * @return array<string,mixed>
     */
    private function baseEntry(string $objectType, int $objectId, array $fields): array
    {
        $subtype = isset($fields['object_subtype']) ? sanitize_key((string) $fields['object_subtype']) : '';
        return [
            'object_key' => $objectType . ':' . $objectId,
            'object_type' => $objectType,
            'object_id' => $objectId,
            'object_subtype' => $subtype,
            'object_title' => $this->resolveObjectTitle($objectType, $objectId),
            'object_url' => $this->resolveObjectUrl($objectType, $objectId, $subtype),
            'generate_status' => '',
            'apply_status' => '',
            'fix_status' => '',
            'suggestion_source' => '',
            'suggested_title' => '',
            'suggested_description' => '',
            'before_title' => '',
            'before_description' => '',
            'after_title' => '',
            'after_description' => '',
            'changed_fields' => [],
            'updated_at' => time(),
        ];
    }

    private function resolveObjectTitle(string $objectType, int $objectId): string
    {
        if ($objectType === 'term') {
            $term = get_term($objectId);
            if ($term && !is_wp_error($term) && isset($term->name)) {
                return trim((string) $term->name);
            }
            return '';
        }

        return trim((string) get_the_title($objectId));
    }

    private function resolveObjectUrl(string $objectType, int $objectId, string $subtype): string
    {
        if ($objectType === 'term') {
            $term = get_term($objectId);
            if ($term && !is_wp_error($term)) {
                $url = get_term_link($term);
                if (!is_wp_error($url)) {
                    return (string) $url;
                }
            }
            if ($subtype !== '') {
                $url = get_term_link($objectId, $subtype);
                if (!is_wp_error($url)) {
                    return (string) $url;
                }
            }
            return '';
        }

        $url = get_permalink($objectId);
        return is_string($url) ? $url : '';
    }

    /**
     * Keep report meta JSON-friendly while still allowing nested arrays.
     *
     * @param mixed $value
     * @return mixed
     */
    private function sanitizeMetaValue($value)
    {
        if (is_scalar($value) || $value === null) {
            return $value;
        }

        if (is_array($value)) {
            $out = [];
            foreach ($value as $key => $item) {
                $clean = $this->sanitizeMetaValue($item);
                if ($clean === null && $item !== null) {
                    continue;
                }
                $out[is_int($key) ? $key : (string) $key] = $clean;
            }
            return $out;
        }

        return null;
    }

    private function optionName(string $sessionId): string
    {
        return self::OPTION_PREFIX . sanitize_key($sessionId);
    }
}
