<?php

namespace WPSN\Modules\BulkTable\Admin;

use WPSN\Core\Container;

defined('ABSPATH') || exit;

final class TermBulkTableJsonImporter
{
    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} $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';
        }

        $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,
            ];
        }

        $changes = [];
        $ops = [];
        $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 !== 'term') {
                continue;
            }

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

            $taxonomy = isset($item['taxonomy']) ? sanitize_key((string) $item['taxonomy']) : '';
            if ($taxonomy === '' || !taxonomy_exists($taxonomy)) {
                $errors[] = sprintf(__('Item %d: invalid taxonomy.', 'wp-seo-next'), $rowNum);
                continue;
            }

            $resolved = $this->resolveTermFromItem($item, $taxonomy, $mapping);
            $termId = $resolved['term_id'];
            $mappedBy = $resolved['mapped_by'];
            $term = $resolved['term'];

            if ($termId <= 0 || !$term) {
                $errors[] = sprintf(__('Item %d: could not map item to a term (id/slug).', 'wp-seo-next'), $rowNum);
                continue;
            }

            if (!$this->canEditTerm($termId)) {
                $errors[] = sprintf(__('Item %d: no permission to edit term #%d.', 'wp-seo-next'), $rowNum, $termId);
                continue;
            }

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

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

                $changedFields[$field] = $newValue;

                $changes[] = [
                    'term_id' => $termId,
                    'taxonomy' => $taxonomy,
                    'term_name' => is_object($term) ? (string) ($term->name ?? '') : '',
                    'mapped_by' => $mappedBy,
                    'field' => $field,
                    'old' => (string) $old,
                    'new' => ($newValue === TermBulkTableImporter::CLEAR_MARKER) ? (string) __('(clear)', 'wp-seo-next') : (string) $newValue,
                ];

                $totalChanges++;
            }

            if (!empty($changedFields)) {
                $ops[] = [
                    'term_id' => $termId,
                    'taxonomy' => $taxonomy,
                    'fields' => $changedFields,
                ];
            }
        }

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

    /**
     * @param array<string,mixed> $item
     * @return array{term_id:int,mapped_by:string,term:?\WP_Term}
     */
    private function resolveTermFromItem(array $item, string $taxonomy, string $mapping): array
    {
        // 1) Try id.
        $idRaw = $item['id'] ?? ($item['term_id'] ?? null);
        $id = is_numeric($idRaw) ? (int) $idRaw : 0;
        if ($id > 0) {
            $maybe = get_term($id, $taxonomy);
            if ($maybe && !is_wp_error($maybe)) {
                return [
                    'term_id' => (int) $id,
                    'mapped_by' => 'id',
                    'term' => $maybe instanceof \WP_Term ? $maybe : null,
                ];
            }
        }

        if ($mapping === 'id_only') {
            return [
                'term_id' => 0,
                'mapped_by' => 'id',
                'term' => null,
            ];
        }

        // 2) Try slug.
        $slug = '';
        if (isset($item['slug']) && is_string($item['slug'])) {
            $slug = sanitize_title($item['slug']);
        }
        if ($slug === '' && isset($item['link']) && is_string($item['link'])) {
            $path = (string) (parse_url($item['link'], PHP_URL_PATH) ?? '');
            $path = trim($path, '/');
            if ($path !== '') {
                $parts = explode('/', $path);
                $slug = sanitize_title((string) end($parts));
            }
        }

        if ($slug !== '') {
            $maybe = get_term_by('slug', $slug, $taxonomy);
            if ($maybe && !is_wp_error($maybe)) {
                return [
                    'term_id' => (int) ($maybe->term_id ?? 0),
                    'mapped_by' => 'slug',
                    'term' => $maybe instanceof \WP_Term ? $maybe : null,
                ];
            }
        }

        return [
            'term_id' => 0,
            'mapped_by' => 'slug',
            'term' => null,
        ];
    }

    private function canEditTerm(int $termId): bool
    {
        if (defined('WP_CLI') && WP_CLI) {
            return true;
        }
        return current_user_can('edit_term', $termId);
    }

    /**
     * @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];

            if ($v === null) {
                $fields[$k] = TermBulkTableImporter::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] = TermBulkTableImporter::CLEAR_MARKER;
                }
                continue;
            }

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

            $fields[$k] = $v;
        }

        return $fields;
    }

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