<?php

namespace App\Category\Service;

use Doctrine\DBAL\Connection;
use Symfony\Component\Uid\Uuid;

/**
 * Shared bulk export/import logic for the six category bundles
 * (provinces, cities, specialties, doctor_services, insurances, tags).
 *
 * Used by both the admin HTTP endpoint (CategoryImportController) and the
 * `app:seed-categories` console command, so validation + the wipe+replace
 * behaviour live in exactly one place.
 */
class CategoryImporter
{
    /** bundle => table name */
    public const TABLES = [
        'provinces'       => 'provinces',
        'cities'          => 'cities',
        'specialties'     => 'specialties',
        'doctor_services' => 'doctor_services',
        'insurances'      => 'insurances',
        'tags'            => 'tags',
    ];

    public function __construct(
        private readonly Connection $db,
    ) {}

    public function isValidBundle(string $bundle): bool
    {
        return isset(self::TABLES[$bundle]);
    }

    /** Complete export of a bundle — every row, normalized numeric columns. */
    public function exportAll(string $bundle): array
    {
        $table = self::TABLES[$bundle];
        // cities export must match the shape nobat724_front/data/city.json consumes:
        // social_media as an object (not the raw JSON string) and province_name included.
        $sql = $bundle === 'cities'
            ? 'SELECT c.*, p.name AS province_name FROM cities c LEFT JOIN provinces p ON p.id = c.province_id ORDER BY c.id ASC'
            : 'SELECT * FROM ' . $table . ' ORDER BY id ASC';
        $rows = $this->db->fetchAllAssociative($sql);
        foreach ($rows as &$r) {
            foreach (['id', 'status', 'weight', 'parent_id', 'specialty_id', 'province_id', 'representation_id'] as $intCol) {
                if (array_key_exists($intCol, $r) && $r[$intCol] !== null) {
                    $r[$intCol] = (int) $r[$intCol];
                }
            }
            if ($bundle === 'cities' && is_string($r['social_media'] ?? null)) {
                $r['social_media'] = json_decode($r['social_media'], true);
            }
        }
        unset($r);
        return $rows;
    }

    /**
     * Strict, two-phase validation. Returns a list of
     * ['row' => int, 'field' => string, 'message' => string]; empty == valid.
     * On success $normalized is filled with ready-to-insert column maps.
     */
    public function validate(string $bundle, array $payload, array &$normalized): array
    {
        $errors     = [];
        $normalized = [];
        $seenId     = [];
        $seenSlug   = [];

        foreach ($payload as $i => $raw) {
            $rowNo = $i + 1;
            $add = function (string $field, string $message) use (&$errors, $rowNo): void {
                $errors[] = ['row' => $rowNo, 'field' => $field, 'message' => "ردیف {$rowNo}: {$message}"];
            };

            if (!is_array($raw) || array_keys($raw) === range(0, count($raw) - 1)) {
                $add('-', 'ساختار رکورد نامعتبر است');
                continue;
            }

            $id = $raw['id'] ?? null;
            if (!self::isPositiveInt($id)) {
                $add('id', 'شناسه (id) باید عدد صحیح مثبت باشد');
            } elseif (isset($seenId[(int) $id])) {
                $add('id', "شناسه {$id} تکراری است (ردیف {$seenId[(int) $id]})");
            } else {
                $seenId[(int) $id] = $rowNo;
            }

            $name = $raw['name'] ?? null;
            if (!is_string($name) || trim($name) === '') {
                $add('name', 'نام الزامی است');
            } elseif (mb_strlen($name) > 255) {
                $add('name', 'نام نباید بیش از ۲۵۵ نویسه باشد');
            }

            $status = self::normInt($raw['status'] ?? 1);
            if ($status === null || !in_array($status, [0, 1], true)) {
                $add('status', 'وضعیت باید ۰ یا ۱ باشد');
            }

            $row = [
                'id'     => self::isPositiveInt($id) ? (int) $id : null,
                'uuid'   => (is_string($raw['uuid'] ?? null) && $raw['uuid'] !== '') ? mb_substr($raw['uuid'], 0, 36) : Uuid::v4()->toRfc4122(),
                'name'   => is_string($name) ? trim($name) : '',
                'status' => $status ?? 1,
            ];

            if (in_array($bundle, ['provinces', 'cities', 'specialties', 'doctor_services'], true)) {
                $weight = self::normInt($raw['weight'] ?? 0);
                if ($weight === null || $weight < 0) {
                    $add('weight', 'ترتیب نمایش باید عدد صحیح نامنفی باشد');
                }
                $row['weight'] = $weight ?? 0;
            }

            if (in_array($bundle, ['specialties', 'doctor_services', 'tags'], true)) {
                $slug = $raw['slug'] ?? null;
                if (!is_string($slug) || trim($slug) === '') {
                    $add('slug', 'slug الزامی است');
                } elseif (mb_strlen($slug) > 255) {
                    $add('slug', 'slug نباید بیش از ۲۵۵ نویسه باشد');
                } else {
                    $slug = trim($slug);
                    if (isset($seenSlug[$slug])) {
                        $add('slug', "slug «{$slug}» تکراری است (ردیف {$seenSlug[$slug]})");
                    } else {
                        $seenSlug[$slug] = $rowNo;
                    }
                    $row['slug'] = $slug;
                }
            }

            switch ($bundle) {
                case 'specialties':
                    $row['parent_id'] = self::nullableId($raw['parent_id'] ?? null, $add, 'parent_id');
                    break;
                case 'doctor_services':
                    $row['specialty_id'] = self::nullableId($raw['specialty_id'] ?? null, $add, 'specialty_id');
                    break;
                case 'insurances':
                    $type = $raw['type'] ?? null;
                    if (!in_array($type, ['basic', 'supplementary'], true)) {
                        $add('type', 'نوع بیمه باید basic یا supplementary باشد');
                    }
                    $row['type']     = is_string($type) ? $type : 'basic';
                    $row['logo_url'] = self::optStr($raw['logo_url'] ?? null);
                    break;
                case 'cities':
                    $row['site_name']          = self::optStr($raw['site_name'] ?? null);
                    $row['title']              = self::optStr($raw['title'] ?? null);
                    $row['province_id']        = self::nullableId($raw['province_id'] ?? null, $add, 'province_id');
                    $row['representation_id']  = self::nullableId($raw['representation_id'] ?? null, $add, 'representation_id');
                    $row['contact_phone']      = self::optStr($raw['contact_phone'] ?? null);
                    $row['email']              = self::optStr($raw['email'] ?? null);
                    $row['description']        = self::optStr($raw['description'] ?? null);
                    $row['slogan']             = self::optStr($raw['slogan'] ?? null);
                    $row['domain']             = self::optStr($raw['domain'] ?? null);
                    $row['keywords']           = self::optStr($raw['keywords'] ?? null);
                    $row['footer_description'] = self::optStr($raw['footer_description'] ?? null);
                    $row['logo_url']           = self::optStr($raw['logo_url'] ?? null);
                    $sm = $raw['social_media'] ?? null;
                    $row['social_media']       = ($sm === null || $sm === '') ? null : (is_string($sm) ? $sm : json_encode($sm, JSON_UNESCAPED_UNICODE));
                    break;
            }

            $normalized[] = $row;
        }

        $this->validateReferences($bundle, $normalized, $seenId, $errors);

        return $errors;
    }

    /** Wipe the bundle's table and insert the normalized rows in one transaction. */
    public function replace(string $bundle, array $normalized): void
    {
        $table = self::TABLES[$bundle];
        $this->db->beginTransaction();
        try {
            $this->db->executeStatement('SET FOREIGN_KEY_CHECKS = 0');
            $this->db->executeStatement('DELETE FROM ' . $table);
            foreach ($normalized as $row) {
                $this->db->insert($table, $row);
            }
            $this->db->executeStatement('SET FOREIGN_KEY_CHECKS = 1');
            $this->db->commit();
        } catch (\Throwable $e) {
            $this->db->rollBack();
            try { $this->db->executeStatement('SET FOREIGN_KEY_CHECKS = 1'); } catch (\Throwable) {}
            throw $e;
        }
    }

    private function validateReferences(string $bundle, array $rows, array $seenId, array &$errors): void
    {
        $err = function (int $rowNo, string $field, string $message) use (&$errors): void {
            $errors[] = ['row' => $rowNo, 'field' => $field, 'message' => "ردیف {$rowNo}: {$message}"];
        };

        if ($bundle === 'specialties') {
            foreach ($rows as $idx => $r) {
                $p = $r['parent_id'] ?? null;
                if ($p !== null && !isset($seenId[$p])) {
                    $err($idx + 1, 'parent_id', "والد با شناسه {$p} در همین فایل وجود ندارد");
                }
            }
            return;
        }

        if ($bundle === 'doctor_services') {
            $valid = $this->existingIds('specialties');
            foreach ($rows as $idx => $r) {
                $s = $r['specialty_id'] ?? null;
                if ($s !== null && !isset($valid[$s])) {
                    $err($idx + 1, 'specialty_id', "تخصص با شناسه {$s} در سیستم وجود ندارد");
                }
            }
            return;
        }

        if ($bundle === 'cities') {
            $validProv = $this->existingIds('provinces');
            $validRep  = $this->existingIds('representations');
            foreach ($rows as $idx => $r) {
                $pv = $r['province_id'] ?? null;
                if ($pv !== null && !isset($validProv[$pv])) {
                    $err($idx + 1, 'province_id', "استان با شناسه {$pv} وجود ندارد");
                }
                $rp = $r['representation_id'] ?? null;
                if ($rp !== null && !isset($validRep[$rp])) {
                    $err($idx + 1, 'representation_id', "نماینده با شناسه {$rp} وجود ندارد");
                }
            }
        }
    }

    /** @return array<int,true> */
    private function existingIds(string $table): array
    {
        $set = [];
        foreach ($this->db->fetchFirstColumn('SELECT id FROM ' . $table) as $id) {
            $set[(int) $id] = true;
        }
        return $set;
    }

    private static function isPositiveInt(mixed $v): bool
    {
        return (is_int($v) || (is_string($v) && ctype_digit($v))) && (int) $v > 0;
    }

    private static function normInt(mixed $v): ?int
    {
        if (is_int($v)) return $v;
        if (is_string($v) && preg_match('/^-?\d+$/', $v)) return (int) $v;
        return null;
    }

    private static function nullableId(mixed $v, callable $add, string $field): ?int
    {
        if ($v === null || $v === '' || $v === 0 || $v === '0') return null;
        if (!self::isPositiveInt($v)) {
            $add($field, "{$field} باید عدد صحیح مثبت یا خالی باشد");
            return null;
        }
        return (int) $v;
    }

    private static function optStr(mixed $v): ?string
    {
        if (!is_string($v)) return null;
        $v = trim($v);
        return $v === '' ? null : $v;
    }
}
