<?php
declare(strict_types=1);

namespace App\Taxonomy;

use App\Slug\Slugger;
use App\Support\TranslatableException;
use InvalidArgumentException;
use PDO;
use PDOException;
use RuntimeException;
use Throwable;

final class TagCreationService
{
    private readonly Slugger $slugger;

    public function __construct(private readonly PDO $pdo)
    {
        $this->slugger = new Slugger();
    }

    /** @return array{id:int,name:string,slug:string,content_locale:string,created:bool} */
    public function createOrFind(int $userId, string $name, string $contentLocale): array
    {
        $this->requireActiveUser($userId);
        $locale = $this->normalizeEnabledLocale($contentLocale);
        $displayName = $this->normalizeName($name);

        $existing = $this->findByLocaleAndName($locale, $displayName);
        if (is_array($existing)) {
            $existing['created'] = false;
            return $existing;
        }

        $this->pdo->beginTransaction();
        try {
            $slug = $this->slugger->unique(
                $displayName,
                function (string $candidate): bool {
                    $statement = $this->pdo->prepare('SELECT 1 FROM tags WHERE slug=:slug LIMIT 1');
                    $statement->execute(['slug' => $candidate]);
                    return $statement->fetchColumn() !== false;
                }
            );

            $statement = $this->pdo->prepare(
                'INSERT INTO tags (name,slug,content_locale,created_at,updated_at) VALUES (:name,:slug,:locale,NOW(),NOW())'
            );
            $statement->execute(['name' => $displayName, 'slug' => $slug, 'locale' => $locale]);
            $id = (int) $this->pdo->lastInsertId();
            $this->pdo->commit();

            return ['id' => $id, 'name' => $displayName, 'slug' => $slug, 'content_locale' => $locale, 'created' => true];
        } catch (PDOException $exception) {
            if ($this->pdo->inTransaction()) $this->pdo->rollBack();
            if ((string) $exception->getCode() === '23000') {
                $existing = $this->findByLocaleAndName($locale, $displayName);
                if (is_array($existing)) {
                    $existing['created'] = false;
                    return $existing;
                }
                $legacyCollision = $this->findByNameAnyLocale($displayName);
                if (is_array($legacyCollision) && (string) ($legacyCollision['content_locale'] ?? '') !== $locale) {
                    throw new TranslatableException('api.database_upgrade_required', [], 'Database upgrade is required before the same tag name can be used in another language.');
                }
            }
            throw $exception;
        } catch (Throwable $throwable) {
            if ($this->pdo->inTransaction()) $this->pdo->rollBack();
            throw $throwable;
        }
    }

    private function requireActiveUser(int $userId): void
    {
        if ($userId <= 0) throw new TranslatableException('auth.authentication_required');
        $statement = $this->pdo->prepare("SELECT status FROM users WHERE id=:id LIMIT 1");
        $statement->execute(['id' => $userId]);
        if ((string) $statement->fetchColumn() !== 'active') {
            throw new TranslatableException('auth.account_inactive');
        }
    }

    private function normalizeEnabledLocale(string $locale): string
    {
        $locale = strtolower(str_replace('_', '-', trim($locale)));
        if ($locale === '' || preg_match('/^[a-z]{2,3}(?:-[a-z0-9]{2,8})*$/', $locale) !== 1) {
            throw new TranslatableException('taxonomy.locale_invalid');
        }
        $statement = $this->pdo->prepare('SELECT 1 FROM languages WHERE code=:code AND is_enabled=1 LIMIT 1');
        $statement->execute(['code' => $locale]);
        if ($statement->fetchColumn() === false) throw new TranslatableException('taxonomy.locale_not_enabled');
        return $locale;
    }

    private function normalizeName(string $name): string
    {
        $name = trim((string) preg_replace('/\s+/u', ' ', trim($name)));
        if (function_exists('mb_strlen')) {
            $length = mb_strlen($name, 'UTF-8');
        } else {
            $length = preg_match_all('/./us', $name, $matches);
            if ($length === false) $length = strlen($name);
        }
        if ($length < 2 || $length > 64) throw new TranslatableException('content.tag_name_length', ['min' => 2, 'max' => 64]);
        if (preg_match('/[\x00-\x1F\x7F<>]/u', $name) === 1) throw new TranslatableException('tags.unsupported_characters');
        if (preg_match('~(?:https?://|www\.|\.[a-z]{2,}(?:/|$))~iu', $name) === 1) throw new TranslatableException('tags.no_url');
        return $name;
    }

    /** @return array{id:int,name:string,slug:string,content_locale:string}|null */
    private function findByNameAnyLocale(string $name): ?array
    {
        $statement = $this->pdo->prepare('SELECT id,name,slug,content_locale FROM tags WHERE name=:name LIMIT 1');
        $statement->execute(['name' => $name]);
        $row = $statement->fetch(PDO::FETCH_ASSOC);
        if (!is_array($row)) return null;
        return [
            'id' => (int) ($row['id'] ?? 0),
            'name' => (string) ($row['name'] ?? ''),
            'slug' => (string) ($row['slug'] ?? ''),
            'content_locale' => (string) ($row['content_locale'] ?? ''),
        ];
    }

    /** @return array{id:int,name:string,slug:string,content_locale:string}|null */
    private function findByLocaleAndName(string $locale, string $name): ?array
    {
        $statement = $this->pdo->prepare(
            'SELECT id,name,slug,content_locale FROM tags WHERE content_locale=:locale AND name=:name LIMIT 1'
        );
        $statement->execute(['locale' => $locale, 'name' => $name]);
        $row = $statement->fetch(PDO::FETCH_ASSOC);
        if (!is_array($row)) return null;
        return [
            'id' => (int) ($row['id'] ?? 0),
            'name' => (string) ($row['name'] ?? ''),
            'slug' => (string) ($row['slug'] ?? ''),
            'content_locale' => (string) ($row['content_locale'] ?? ''),
        ];
    }
}
