<?php

namespace WPSN\Modules\BulkTable\Admin;

use WPSN\Core\Container;

defined('ABSPATH') || exit;

/**
 * JSON importer for SEO overrides.
 *
 * Intentionally parallel to the CSV importer so we can reuse the same apply logic
 * (ops -> BulkTableImporter::applyOps()).
 */
final class BulkTableJsonImporter
{
    private Container $container;

    public function __construct(Container $container)
    {
        $this->container = $container;
    }

    /**
     * @param array{apply_only_changed?:bool,empty_cells?:string,clear_token?:string,mapping?:string,rewrite_rules?:string} $options
     * @return array{errors:array<int,string>,changes:array<int,array<string,mixed>>,ops:array<int,array<string,mixed>>,total_rows:int,total_changes:int}
     */
    public function parseAndDiff(string $filePath, array $options = []): array
    {
        $errors = [];

        $applyOnlyChanged = array_key_exists('apply_only_changed', $options) ? (bool) $options['apply_only_changed'] : true;
        $emptyCells = isset($options['empty_cells']) ? (string) $options['empty_cells'] : 'ignore';
        if (!in_array($emptyCells, ['ignore', 'clear'], true)) {
            $emptyCells = 'ignore';
        }

        $clearToken = isset($options['clear_token']) ? trim((string) $options['clear_token']) : '__CLEAR__';
        if ($clearToken === '') {
            $clearToken = '__CLEAR__';
        }

        $mapping = isset($options['mapping']) ? sanitize_key((string) $options['mapping']) : 'auto';
        if (!in_array($mapping, ['auto', 'id_only'], true)) {
            $mapping = 'auto';
        }

        $rewriteRulesRaw = isset($options['rewrite_rules']) ? (string) $options['rewrite_rules'] : '';
        $rewriteRules = PermalinkRewrite::parseRules($rewriteRulesRaw);

        $content = file_get_contents($filePath);
        if ($content === false) {
            return [
                'errors' => [__('Unable to read uploaded JSON file.', 'wp-seo-next')],
                'changes' => [],
                'ops' => [],
                'total_rows' => 0,
                'total_changes' => 0,
            ];
        }

        $data = json_decode($content, true);
        if (!is_array($data)) {
            return [
                'errors' => [__('Invalid JSON file.', 'wp-seo-next')],
                'changes' => [],
                'ops' => [],
                'total_rows' => 0,
                'total_changes' => 0,
            ];
        }

        $items = $data['items'] ?? [];
        if (!is_array($items)) {
            return [
                'errors' => [__('JSON must contain an "items" array.', 'wp-seo-next')],
                'changes' => [],
                'ops' => [],
                'total_rows' => 0,
                'total_changes' => 0,
            ];
        }

        $ops = [];
        $changes = [];
        $totalRows = 0;
        $totalChanges = 0;

        foreach ($items as $i => $item) {
            if (!is_array($item)) {
                continue;
            }

            $type = isset($item['type']) ? sanitize_key((string) $item['type']) : '';
            if ($type !== 'post') {
                continue;
            }

            $totalRows++;
            $rowNum = $i + 1;

            $resolved = $this->resolvePostFromItem($item, $mapping, $rewriteRules);
            $postId = $resolved['post_id'];
            $mappedBy = $resolved['mapped_by'];
            $post = $resolved['post'];
            $origPath = isset($resolved['orig_path']) ? (string) $resolved['orig_path'] : '';
            $rewrittenPath = isset($resolved['rewritten_path']) ? (string) $resolved['rewritten_path'] : '';

            if ($postId <= 0 || !$post) {
                $ctx = '';
                if ($origPath !== '') {
                    if ($rewrittenPath !== '' && $rewrittenPath !== $origPath) {
                        $ctx = ' ' . sprintf(
                            /* translators: 1: original path, 2: rewritten path */
                            __('Permalink path: %1$s → %2$s.', 'wp-seo-next'),
                            $origPath,
                            $rewrittenPath
                        );
                    } else {
                        $ctx = ' ' . sprintf(
                            /* translators: %s: permalink path */
                            __('Permalink path: %s.', 'wp-seo-next'),
                            $origPath
                        );
                    }
                }

                $errors[] = sprintf(
                    /* translators: 1: item index, 2: mapping context */
                    __('Item %1$d: could not map item to a post (id/permalink/slug).%2$s', 'wp-seo-next'),
                    $rowNum,
                    $ctx
                );
                continue;
            }

            if (!$this->canEditPost($postId)) {
                $errors[] = sprintf(
                    /* translators: 1: item index, 2: post ID */
                    __('Item %1$d: no permission to edit post #%2$d.', 'wp-seo-next'),
                    $rowNum,
                    $postId
                );
                continue;
            }

            $fields = $this->extractFieldsFromItem($item, $emptyCells, $clearToken);
            if (empty($fields)) {
                continue;
            }

            $changedFields = [];
            foreach ($fields as $field => $newValue) {
                $old = $this->getCurrentValue($postId, $field);
                $compareNew = ($newValue === BulkTableImporter::CLEAR_MARKER) ? '' : (string) $newValue;
                if ($applyOnlyChanged && trim((string) $old) === trim((string) $compareNew)) {
                    continue;
                }

                $changedFields[$field] = $newValue;

                $changes[] = [
                    'post_id' => $postId,
                    'post_title' => (string) get_the_title($postId),
                    'mapped_by' => $mappedBy,
                    'field' => $field,
                    'old' => (string) $old,
                    'new' => ($newValue === BulkTableImporter::CLEAR_MARKER) ? (string) __('(clear)', 'wp-seo-next') : (string) $newValue,
                ];

                $totalChanges++;
            }

            if (!empty($changedFields)) {
                $ops[] = [
                    'post_id' => $postId,
                    'fields' => $changedFields,
                ];
            }
        }

        return [
            'errors' => $errors,
            'changes' => $changes,
            'ops' => $ops,
            'total_rows' => $totalRows,
            'total_changes' => $totalChanges,
        ];
    }

    /**
     * @param array<string,mixed> $item
     * @param array<int,array{from:string,to:string,kind:string}> $rewriteRules
     * @return array{post_id:int,mapped_by:string,post: ?\WP_Post,orig_path:string,rewritten_path:string}
     */
    private function resolvePostFromItem(array $item, string $mapping, array $rewriteRules = []): array
    {
        $origPath = '';
        $rewrittenPath = '';

        // 1) Try id.
        $idRaw = $item['id'] ?? ($item['post_id'] ?? null);
        $id = is_numeric($idRaw) ? (int) $idRaw : 0;
        if ($id > 0) {
            $maybe = get_post($id);
            if ($maybe instanceof \WP_Post) {
                return [
                    'post_id' => $id,
                    'mapped_by' => 'id',
                    'post' => $maybe,
                    'orig_path' => '',
                    'rewritten_path' => '',
                ];
            }
        }

        if ($mapping === 'id_only') {
            return [
                'post_id' => 0,
                'mapped_by' => 'id',
                'post' => null,
                'orig_path' => '',
                'rewritten_path' => '',
            ];
        }

        // 2) Try permalink.
        $rawPermalink = '';
        if (isset($item['permalink']) && is_string($item['permalink'])) {
            $rawPermalink = trim($item['permalink']);
        } elseif (isset($item['url']) && is_string($item['url'])) {
            $rawPermalink = trim($item['url']);
        }

        if ($rawPermalink !== '') {
            $origPath = (string) (parse_url($rawPermalink, PHP_URL_PATH) ?? '');
            $origPath = '/' . ltrim($origPath, '/');

            $rewrittenPath = $origPath;
            if (!empty($rewriteRules)) {
                $rewrittenPath = PermalinkRewrite::rewritePathFromUrl($rawPermalink, $rewriteRules);
            }

            $path = $rewrittenPath;
            if ($path !== '/' && $path !== '') {
                $local = home_url($path);
                $pid = (int) url_to_postid($local);
                if ($pid > 0) {
                    $maybe = get_post($pid);
                    if ($maybe instanceof \WP_Post) {
                        return [
                            'post_id' => $pid,
                            'mapped_by' => (!empty($rewriteRules) && $path !== $origPath) ? 'permalink(rewrite)' : 'permalink',
                            'post' => $maybe,
                            'orig_path' => $origPath,
                            'rewritten_path' => $rewrittenPath,
                        ];
                    }
                }
            }
        }

        // 3) Try slug (+ optional post_type).
        $slug = '';
        if (isset($item['slug']) && is_string($item['slug'])) {
            $slug = sanitize_title($item['slug']);
        }
        if ($slug === '' && $rawPermalink !== '') {
            $pathForSlug = (string) (parse_url($rawPermalink, PHP_URL_PATH) ?? '');
            $pathForSlug = '/' . ltrim($pathForSlug, '/');
            if (!empty($rewriteRules)) {
                $pathForSlug = PermalinkRewrite::rewritePathFromUrl($rawPermalink, $rewriteRules);
            }
            $pathForSlug = trim($pathForSlug, '/');
            if ($pathForSlug !== '') {
                $parts = explode('/', $pathForSlug);
                $slug = sanitize_title((string) end($parts));
            }
        }

        $postType = '';
        if (isset($item['post_type']) && is_string($item['post_type'])) {
            $postType = sanitize_key($item['post_type']);
        }

        if ($slug !== '') {
            $args = [
                'name' => $slug,
                'post_type' => $postType !== '' ? $postType : 'any',
                'post_status' => 'any',
                'posts_per_page' => 2,
                'fields' => 'ids',
                'no_found_rows' => true,
                'suppress_filters' => false,
            ];

            $q = new \WP_Query($args);
            $ids = is_array($q->posts) ? $q->posts : [];
            if (count($ids) === 1) {
                $pid = (int) $ids[0];
                if ($pid > 0) {
                    $maybe = get_post($pid);
                    if ($maybe instanceof \WP_Post) {
                        return [
                            'post_id' => $pid,
                            'mapped_by' => 'slug',
                            'post' => $maybe,
                            'orig_path' => $origPath,
                            'rewritten_path' => $rewrittenPath,
                        ];
                    }
                }
            }
        }

        return [
            'post_id' => 0,
            'mapped_by' => 'id',
            'post' => null,
            'orig_path' => $origPath,
            'rewritten_path' => $rewrittenPath,
        ];
    }

    private function canEditPost(int $postId): bool
    {
        if (defined('WP_CLI') && WP_CLI) {
            return true;
        }
        return current_user_can('edit_post', $postId);
    }

    /**
     * @param array<string,mixed> $item
     * @return array<string,string>
     */
    private function extractFieldsFromItem(array $item, string $emptyCells, string $clearToken): array
    {
        $ov = $item['overrides'] ?? [];
        if (!is_array($ov)) {
            return [];
        }

        $known = [
            'seo_title',
            'meta_description',
            'canonical',
            'robots_index',
            'robots_follow',
            'og_title',
            'og_description',
            'og_image',
            'twitter_title',
            'twitter_description',
            'twitter_image',
        ];

        $fields = [];
        foreach ($known as $k) {
            if (!array_key_exists($k, $ov)) {
                continue;
            }

            $v = $ov[$k];

            // JSON native clear: null.
            if ($v === null) {
                $fields[$k] = BulkTableImporter::CLEAR_MARKER;
                continue;
            }

            if (is_bool($v) || is_int($v) || is_float($v)) {
                $v = (string) $v;
            }

            if (!is_string($v)) {
                continue;
            }

            $v = trim($v);
            if ($v === '') {
                if ($emptyCells === 'clear') {
                    $fields[$k] = BulkTableImporter::CLEAR_MARKER;
                }
                continue;
            }

            if ($v === $clearToken) {
                $fields[$k] = BulkTableImporter::CLEAR_MARKER;
                continue;
            }

            $fields[$k] = $v;
        }

        return $fields;
    }

    private function getCurrentValue(int $postId, string $field): string
    {
        switch ($field) {
            case 'seo_title':
                return (string) get_post_meta($postId, \WPSN\SEO\MetaKeys::TITLE, true);
            case 'meta_description':
                return (string) get_post_meta($postId, \WPSN\SEO\MetaKeys::DESCRIPTION, true);
            case 'canonical':
                return (string) get_post_meta($postId, \WPSN\SEO\MetaKeys::CANONICAL, true);
            case 'robots_index':
                return (string) get_post_meta($postId, \WPSN\SEO\MetaKeys::ROBOTS_INDEX, true);
            case 'robots_follow':
                return (string) get_post_meta($postId, \WPSN\SEO\MetaKeys::ROBOTS_FOLLOW, true);
            case 'og_title':
                return (string) get_post_meta($postId, \WPSN\SEO\MetaKeys::OG_TITLE, true);
            case 'og_description':
                return (string) get_post_meta($postId, \WPSN\SEO\MetaKeys::OG_DESCRIPTION, true);
            case 'og_image':
                return (string) get_post_meta($postId, \WPSN\SEO\MetaKeys::OG_IMAGE, true);
            case 'twitter_title':
                return (string) get_post_meta($postId, \WPSN\SEO\MetaKeys::TWITTER_TITLE, true);
            case 'twitter_description':
                return (string) get_post_meta($postId, \WPSN\SEO\MetaKeys::TWITTER_DESCRIPTION, true);
            case 'twitter_image':
                return (string) get_post_meta($postId, \WPSN\SEO\MetaKeys::TWITTER_IMAGE, true);
            default:
                return '';
        }
    }
}
