<?php
declare(strict_types=1);

namespace App\Models;

use PDO;

class Mod extends BaseModel
{
    protected string $table = 'mods';

    public function latestFs25(array $categoryIds = [], int $limit = 8): array
    {
        return $this->list([
            'category_ids' => $categoryIds,
            'sort' => 'newest',
            'limit' => $limit,
        ]);
    }

    public function popular(int $limit = 8): array
    {
        return $this->list(['sort' => 'popular', 'limit' => $limit]);
    }

    public function recommended(int $limit = 8): array
    {
        return $this->list(['sort' => 'recommended', 'limit' => $limit]);
    }

    public function premium(int $limit = 8): array
    {
        return $this->list(['sort' => 'premium', 'limit' => $limit]);
    }

    public function list(array $filters = []): array
    {
        $limit = min(max((int) ($filters['limit'] ?? 24), 1), 60);
        $offset = max((int) ($filters['offset'] ?? 0), 0);
        $sort = (string) ($filters['sort'] ?? 'newest');
        $where = ['m.status = "published"'];
        $params = [];

        if (!empty($filters['q'])) {
            $query = '%' . trim((string) $filters['q']) . '%';
            $where[] = '(m.title LIKE :q_title OR m.description LIKE :q_description OR m.summary LIKE :q_summary OR c.name LIKE :q_category)';
            $params['q_title'] = $query;
            $params['q_description'] = $query;
            $params['q_summary'] = $query;
            $params['q_category'] = $query;
        }

        if (!empty($filters['category_ids']) && is_array($filters['category_ids'])) {
            $ids = array_values(array_filter(array_map('intval', $filters['category_ids'])));
            if ($ids !== []) {
                $placeholders = [];
                foreach ($ids as $index => $id) {
                    $key = 'category_id_' . $index;
                    $placeholders[] = ':' . $key;
                    $params[$key] = $id;
                }
                $where[] = 'm.category_id IN (' . implode(',', $placeholders) . ')';
            }
        }

        if ($sort === 'premium') {
            $where[] = '(m.privacy = "premium" OR m.access_type IN ("subscriber", "paid") OR m.package_id IS NOT NULL)';
        } elseif ($sort === 'free') {
            $where[] = '(m.privacy = "public" AND m.access_type = "free" AND m.package_id IS NULL)';
        }

        $order = match ($sort) {
            'downloaded' => 'm.download_count DESC, m.created_at DESC',
            'viewed' => 'm.view_count DESC, m.created_at DESC',
            'popular' => '(m.download_count * 2 + m.view_count + m.like_count * 6 + m.rating * 250) DESC, m.created_at DESC',
            'recommended' => 'm.featured DESC, m.rating DESC, m.like_count DESC, m.created_at DESC',
            'premium' => 'm.featured DESC, m.rating DESC, m.created_at DESC',
            'free' => 'm.created_at DESC',
            default => 'm.created_at DESC',
        };

        $statement = $this->db()->prepare(
            $this->selectSql() . '
             WHERE ' . implode(' AND ', $where) . "
             ORDER BY {$order}
             LIMIT :limit OFFSET :offset"
        );

        foreach ($params as $key => $value) {
            $statement->bindValue($key, $value, is_int($value) ? PDO::PARAM_INT : PDO::PARAM_STR);
        }
        $statement->bindValue('limit', $limit, PDO::PARAM_INT);
        $statement->bindValue('offset', $offset, PDO::PARAM_INT);
        $statement->execute();

        return array_map(fn (array $mod): array => $this->normalize($mod), $statement->fetchAll());
    }

    public function findBySlug(string $slug): ?array
    {
        $statement = $this->db()->prepare($this->selectSql() . ' WHERE m.slug = :slug AND m.status = "published" LIMIT 1');
        $statement->execute(['slug' => $slug]);
        $mod = $statement->fetch();

        return $mod ? $this->normalize($mod) : null;
    }

    public function adminList(array $filters = [], int $limit = 120): array
    {
        $limit = min(max($limit, 1), 300);
        $where = [];
        $params = [];

        if (!empty($filters['q'])) {
            $where[] = '(m.title LIKE :q_title OR m.slug LIKE :q_slug OR m.summary LIKE :q_summary OR u.username LIKE :q_user)';
            $like = '%' . trim((string) $filters['q']) . '%';
            $params['q_title'] = $like;
            $params['q_slug'] = $like;
            $params['q_summary'] = $like;
            $params['q_user'] = $like;
        }

        if (!empty($filters['category_id'])) {
            $where[] = 'm.category_id = :category_id';
            $params['category_id'] = (int) $filters['category_id'];
        }

        if (!empty($filters['status'])) {
            $where[] = 'm.status = :status';
            $params['status'] = (string) $filters['status'];
        } else {
            $where[] = 'm.status <> "archived"';
        }

        $premium = (string) ($filters['premium'] ?? '');
        if ($premium === 'premium') {
            $where[] = '(m.privacy IN ("premium", "subscriber", "private") OR m.access_type IN ("subscriber", "paid") OR m.package_id IS NOT NULL)';
        } elseif ($premium === 'free') {
            $where[] = '(m.privacy IN ("public", "free") AND m.access_type = "free" AND m.package_id IS NULL)';
        }

        $sql = $this->selectSql();
        if ($where !== []) {
            $sql .= ' WHERE ' . implode(' AND ', $where);
        }
        $sql .= ' ORDER BY FIELD(m.status, "pending", "draft", "published", "rejected", "archived"), m.created_at DESC, m.id DESC LIMIT :limit';

        $statement = $this->db()->prepare($sql);
        foreach ($params as $key => $value) {
            $statement->bindValue($key, $value, is_int($value) ? PDO::PARAM_INT : PDO::PARAM_STR);
        }
        $statement->bindValue('limit', $limit, PDO::PARAM_INT);
        $statement->execute();

        return array_map(fn (array $mod): array => $this->normalize($mod), $statement->fetchAll());
    }

    public function updateStatusByAdmin(int $id, string $status): void
    {
        $allowed = ['draft', 'pending', 'published', 'rejected', 'archived'];
        if (!in_array($status, $allowed, true)) {
            throw new \InvalidArgumentException('Mod durumu gecersiz.');
        }

        $statement = $this->db()->prepare(
            'UPDATE mods
             SET status = :status,
                 published_at = CASE WHEN :published_status = "published" THEN COALESCE(published_at, NOW()) ELSE published_at END,
                 updated_at = NOW()
             WHERE id = :id'
        );
        $statement->execute([
            'status' => $status,
            'published_status' => $status,
            'id' => $id,
        ]);
    }

    public function setFeaturedByAdmin(int $id, bool $featured): void
    {
        $statement = $this->db()->prepare('UPDATE mods SET featured = :featured, updated_at = NOW() WHERE id = :id');
        $statement->execute(['featured' => $featured ? 1 : 0, 'id' => $id]);
    }

    public function findPublishedById(int $id): ?array
    {
        $statement = $this->db()->prepare($this->selectSql() . ' WHERE m.id = :id AND m.status = "published" LIMIT 1');
        $statement->execute(['id' => $id]);
        $mod = $statement->fetch();

        return $mod ? $this->normalize($mod) : null;
    }

    public function publishedForCreator(int $creatorId, int $limit = 24): array
    {
        if ($creatorId <= 0) {
            return [];
        }

        $limit = min(max($limit, 1), 60);
        $statement = $this->db()->prepare(
            $this->selectSql() . '
             WHERE COALESCE(m.user_id, m.creator_id) = :creator_id
               AND m.status IN ("published", "active")
               AND m.privacy <> "private"
             ORDER BY m.featured DESC, COALESCE(m.published_at, m.created_at) DESC, m.id DESC
             LIMIT :limit'
        );
        $statement->bindValue('creator_id', $creatorId, PDO::PARAM_INT);
        $statement->bindValue('limit', $limit, PDO::PARAM_INT);
        $statement->execute();

        return array_map(fn (array $mod): array => $this->normalize($mod), $statement->fetchAll());
    }

    public function related(array $mod, int $limit = 6): array
    {
        return $this->relatedAdvanced($mod, $limit);
    }

    public function relatedAdvanced(array $mod, int $limit = 10): array
    {
        $limit = min(max($limit, 1), 24);
        $seen = [(int) ($mod['id'] ?? 0)];
        $results = [];

        $append = function (array $items) use (&$results, &$seen, $limit): void {
            foreach ($items as $item) {
                $id = (int) ($item['id'] ?? 0);
                if ($id <= 0 || in_array($id, $seen, true)) {
                    continue;
                }
                $seen[] = $id;
                $results[] = $item;
                if (count($results) >= $limit) {
                    return;
                }
            }
        };

        if (!empty($mod['category_id'])) {
            $append($this->fetchRelatedWhere('m.category_id = :category_id', ['category_id' => (int) $mod['category_id']], $seen, $limit));
        }

        if (count($results) < $limit && !empty($mod['category_parent_id'])) {
            $append($this->fetchRelatedWhere(
                '(c.parent_id = :parent_id_child OR c.id = :parent_id_self)',
                [
                    'parent_id_child' => (int) $mod['category_parent_id'],
                    'parent_id_self' => (int) $mod['category_parent_id'],
                ],
                $seen,
                $limit
            ));
        }

        if (count($results) < $limit) {
            $append($this->fetchRelatedWhere('1 = 1', [], $seen, $limit, '(m.download_count * 2 + m.view_count + m.like_count * 6 + m.rating * 250) DESC, m.created_at DESC'));
        }

        if (count($results) < $limit) {
            $append($this->fetchRelatedWhere('1 = 1', [], $seen, $limit, 'm.created_at DESC'));
        }

        return array_slice($results, 0, $limit);
    }

    public function galleryItems(array $mod): array
    {
        $items = [];
        $cover = (string) ($mod['cover_image'] ?? $mod['cover'] ?? '');

        if ($cover !== '' && $cover !== '#') {
            $items[] = [
                'type' => 'image',
                'src' => $cover,
                'thumb' => $cover,
                'label' => (string) ($mod['title'] ?? 'Kapak gorseli'),
                'sort_order' => -10,
            ];
        }

        $statement = $this->db()->prepare(
            'SELECT image_path, sort_order FROM mod_images WHERE mod_id = :mod_id ORDER BY sort_order ASC, id ASC'
        );
        $statement->execute(['mod_id' => (int) $mod['id']]);
        foreach ($statement->fetchAll() as $image) {
            $path = (string) ($image['image_path'] ?? '');
            if ($path === '') {
                continue;
            }

            $items[] = [
                'type' => 'image',
                'src' => $path,
                'thumb' => $path,
                'label' => (string) ($mod['title'] ?? 'Slider gorseli'),
                'sort_order' => (int) ($image['sort_order'] ?? 0),
            ];
        }

        $statement = $this->db()->prepare(
            'SELECT video_url, provider, sort_order FROM mod_videos WHERE mod_id = :mod_id ORDER BY sort_order ASC, id ASC'
        );
        $statement->execute(['mod_id' => (int) $mod['id']]);
        foreach ($statement->fetchAll() as $video) {
            $url = (string) ($video['video_url'] ?? '');
            if ($url === '') {
                continue;
            }

            $provider = $video['provider'] ?: $this->detectVideoProvider($url);
            $items[] = [
                'type' => 'video',
                'src' => $url,
                'embed_url' => $this->videoEmbedUrl($url, $provider),
                'provider' => $provider,
                'thumb' => $cover,
                'label' => ucfirst((string) $provider) . ' video',
                'sort_order' => (int) ($video['sort_order'] ?? 0),
            ];
        }

        try {
            $statement = $this->db()->prepare(
                'SELECT model_path, model_type, thumbnail_path, title, sort_order FROM mod_models WHERE mod_id = :mod_id ORDER BY sort_order ASC, id ASC'
            );
            $statement->execute(['mod_id' => (int) $mod['id']]);
            foreach ($statement->fetchAll() as $model) {
                $path = (string) ($model['model_path'] ?? '');
                if ($path === '') {
                    continue;
                }

                $label = (string) (($model['title'] ?? '') ?: '3D Model');
                $items[] = [
                    'type' => 'model',
                    'src' => $path,
                    'model_type' => (string) (($model['model_type'] ?? '') ?: 'model'),
                    'thumb' => (string) (($model['thumbnail_path'] ?? '') ?: $cover),
                    'label' => $label,
                    'sort_order' => (int) ($model['sort_order'] ?? 0),
                ];
            }
        } catch (\Throwable) {
            // mod_models is added by the 3D gallery migration; older local DBs keep working before import.
        }

        usort($items, static fn (array $a, array $b): int => ((int) $a['sort_order']) <=> ((int) $b['sort_order']));

        return $items;
    }

    public function userReaction(int $modId, ?int $userId): ?string
    {
        if ($userId === null) {
            return null;
        }

        $statement = $this->db()->prepare('SELECT reaction FROM mod_reactions WHERE mod_id = :mod_id AND user_id = :user_id LIMIT 1');
        $statement->execute(['mod_id' => $modId, 'user_id' => $userId]);
        $reaction = $statement->fetchColumn();

        return in_array($reaction, ['like', 'dislike'], true) ? (string) $reaction : null;
    }

    public function recordReaction(int $modId, int $userId, string $reaction): array
    {
        if (!in_array($reaction, ['like', 'dislike'], true)) {
            return $this->reactionCounts($modId) + ['reaction' => null];
        }

        $this->db()->beginTransaction();
        try {
            $current = $this->userReaction($modId, $userId);

            if ($current === $reaction) {
                $statement = $this->db()->prepare('DELETE FROM mod_reactions WHERE mod_id = :mod_id AND user_id = :user_id');
                $statement->execute(['mod_id' => $modId, 'user_id' => $userId]);
                $reaction = '';
            } elseif ($current === null) {
                $statement = $this->db()->prepare(
                    'INSERT INTO mod_reactions (mod_id, user_id, reaction, created_at)
                     VALUES (:mod_id, :user_id, :reaction, NOW())'
                );
                $statement->execute(['mod_id' => $modId, 'user_id' => $userId, 'reaction' => $reaction]);
            } else {
                $statement = $this->db()->prepare(
                    'UPDATE mod_reactions SET reaction = :reaction, created_at = NOW()
                     WHERE mod_id = :mod_id AND user_id = :user_id'
                );
                $statement->execute(['mod_id' => $modId, 'user_id' => $userId, 'reaction' => $reaction]);
            }

            $counts = $this->syncReactionCounts($modId);
            $this->db()->commit();

            return $counts + ['reaction' => $reaction !== '' ? $reaction : null];
        } catch (\Throwable $exception) {
            $this->db()->rollBack();
            throw $exception;
        }
    }

    public function incrementDownloadClick(int $modId, ?int $userId, ?string $ipAddress, string $externalUrl): int
    {
        $this->db()->prepare('UPDATE mods SET download_count = download_count + 1 WHERE id = :id')->execute(['id' => $modId]);

        $statement = $this->db()->prepare(
            'INSERT INTO mod_download_clicks (mod_id, user_id, ip_address, external_url, created_at)
             VALUES (:mod_id, :user_id, :ip_address, :external_url, NOW())'
        );
        $statement->execute([
            'mod_id' => $modId,
            'user_id' => $userId,
            'ip_address' => $ipAddress,
            'external_url' => $externalUrl,
        ]);

        $statement = $this->db()->prepare('SELECT download_count FROM mods WHERE id = :id LIMIT 1');
        $statement->execute(['id' => $modId]);

        return (int) $statement->fetchColumn();
    }

    private function fetchRelatedWhere(string $condition, array $params, array $seen, int $limit, string $order = 'm.featured DESC, m.view_count DESC, m.created_at DESC'): array
    {
        $limit = min(max($limit, 1), 24);
        $excluded = array_values(array_filter(array_map('intval', $seen)));
        $notIn = '';

        if ($excluded !== []) {
            $placeholders = [];
            foreach ($excluded as $index => $id) {
                $key = 'exclude_' . $index;
                $placeholders[] = ':' . $key;
                $params[$key] = $id;
            }
            $notIn = ' AND m.id NOT IN (' . implode(',', $placeholders) . ')';
        }

        $statement = $this->db()->prepare(
            $this->selectSql() . "
             WHERE m.status = \"published\" AND {$condition}{$notIn}
             ORDER BY {$order}
             LIMIT :limit"
        );

        foreach ($params as $key => $value) {
            $statement->bindValue($key, $value, is_int($value) ? PDO::PARAM_INT : PDO::PARAM_STR);
        }
        $statement->bindValue('limit', $limit, PDO::PARAM_INT);
        $statement->execute();

        return array_map(fn (array $item): array => $this->normalize($item), $statement->fetchAll());
    }

    public function recordView(int $modId, ?int $userId = null, ?string $ipAddress = null): void
    {
        $this->db()->prepare('UPDATE mods SET view_count = view_count + 1 WHERE id = :id')->execute(['id' => $modId]);

        $statement = $this->db()->prepare(
            'INSERT INTO mod_views (mod_id, user_id, ip_address, created_at)
             VALUES (:mod_id, :user_id, :ip_address, NOW())'
        );
        $statement->execute([
            'mod_id' => $modId,
            'user_id' => $userId,
            'ip_address' => $ipAddress,
        ]);
    }

    private function reactionCounts(int $modId): array
    {
        $statement = $this->db()->prepare('SELECT like_count, dislike_count FROM mods WHERE id = :id LIMIT 1');
        $statement->execute(['id' => $modId]);
        $counts = $statement->fetch() ?: [];

        return [
            'like_count' => (int) ($counts['like_count'] ?? 0),
            'dislike_count' => (int) ($counts['dislike_count'] ?? 0),
        ];
    }

    private function syncReactionCounts(int $modId): array
    {
        $statement = $this->db()->prepare(
            'SELECT
                SUM(reaction = "like") AS likes,
                SUM(reaction = "dislike") AS dislikes
             FROM mod_reactions
             WHERE mod_id = :mod_id'
        );
        $statement->execute(['mod_id' => $modId]);
        $counts = $statement->fetch() ?: [];
        $likes = (int) ($counts['likes'] ?? 0);
        $dislikes = (int) ($counts['dislikes'] ?? 0);

        $statement = $this->db()->prepare('UPDATE mods SET like_count = :likes, dislike_count = :dislikes WHERE id = :id');
        $statement->execute(['likes' => $likes, 'dislikes' => $dislikes, 'id' => $modId]);

        return ['like_count' => $likes, 'dislike_count' => $dislikes];
    }

    private function detectVideoProvider(string $url): string
    {
        $host = parse_url($url, PHP_URL_HOST) ?: '';
        $host = strtolower((string) $host);

        if (str_contains($host, 'youtu.be') || str_contains($host, 'youtube.com')) {
            return 'youtube';
        }

        if (str_contains($host, 'vimeo.com')) {
            return 'vimeo';
        }

        return 'video';
    }

    private function videoEmbedUrl(string $url, ?string $provider): string
    {
        $provider = $provider ?: $this->detectVideoProvider($url);

        if ($provider === 'youtube') {
            $parts = parse_url($url);
            if (!is_array($parts)) {
                return $url;
            }
            $videoId = '';
            if (($parts['host'] ?? '') === 'youtu.be') {
                $videoId = trim((string) ($parts['path'] ?? ''), '/');
            } elseif (!empty($parts['query'])) {
                parse_str((string) $parts['query'], $query);
                $videoId = (string) ($query['v'] ?? '');
            } elseif (preg_match('~/embed/([a-zA-Z0-9_-]+)~', (string) ($parts['path'] ?? ''), $match)) {
                $videoId = $match[1];
            }

            return $videoId !== '' ? 'https://www.youtube.com/embed/' . rawurlencode($videoId) : $url;
        }

        if ($provider === 'vimeo' && preg_match('~vimeo\.com/(?:video/)?([0-9]+)~', $url, $match)) {
            return 'https://player.vimeo.com/video/' . rawurlencode($match[1]);
        }

        return $url;
    }

    private function selectSql(): string
    {
        return 'SELECT m.*,
                c.name AS category_name,
                c.slug AS category_slug,
                c.parent_id AS category_parent_id,
                pc.name AS category_parent_name,
                pc.slug AS category_parent_slug,
                u.username AS creator_username,
                u.role AS creator_role,
                COALESCE(up.display_name, u.nickname, u.username, "Creator") AS author_name,
                COALESCE(pf.nickname, u.username, "creator") AS author_slug,
                COALESCE(up.avatar, pf.avatar_path) AS author_avatar,
                p.name AS package_name
            FROM mods m
            LEFT JOIN categories c ON c.id = m.category_id
            LEFT JOIN categories pc ON pc.id = c.parent_id
            LEFT JOIN users u ON u.id = COALESCE(m.user_id, m.creator_id)
            LEFT JOIN user_profiles up ON up.user_id = u.id
            LEFT JOIN profiles pf ON pf.user_id = u.id
            LEFT JOIN packages p ON p.id = m.package_id';
    }

    private function normalize(array $mod): array
    {
        $cover = ($mod['cover_image'] ?? '') ?: (($mod['cover_path'] ?? '') ?: '/uploads/covers/mod-1.svg');
        $rating = ($mod['rating'] ?? null) !== null ? (float) $mod['rating'] : 0.0;
        $privacy = $mod['privacy'] ?? 'public';
        $accessType = $mod['access_type'] ?? 'free';
        $isPremium = in_array($privacy, ['premium', 'private', 'subscriber'], true) || in_array($accessType, ['subscriber', 'paid'], true) || !empty($mod['package_id']);

        $mod['id'] = (int) $mod['id'];
        $mod['creator_id'] = isset($mod['creator_id']) ? (int) $mod['creator_id'] : null;
        $mod['user_id'] = isset($mod['user_id']) ? (int) $mod['user_id'] : (isset($mod['creator_id']) ? (int) $mod['creator_id'] : null);
        $mod['category_id'] = $mod['category_id'] === null ? null : (int) $mod['category_id'];
        $mod['category_parent_id'] = isset($mod['category_parent_id']) && $mod['category_parent_id'] !== null ? (int) $mod['category_parent_id'] : null;
        $mod['download_count'] = (int) ($mod['download_count'] ?? 0);
        $mod['view_count'] = (int) ($mod['view_count'] ?? 0);
        $mod['like_count'] = (int) ($mod['like_count'] ?? 0);
        $mod['dislike_count'] = (int) ($mod['dislike_count'] ?? 0);
        $mod['featured'] = (int) ($mod['featured'] ?? 0);
        $mod['rating'] = number_format($rating, 1, '.', '');
        $mod['cover_image'] = $cover;
        $mod['cover'] = $cover;
        $mod['category'] = $mod['category_name'] ?: 'Mods';
        $mod['category_slug'] = $mod['category_slug'] ?: 'fs25-mods';
        $mod['category_parent'] = $mod['category_parent_name'] ?? null;
        $mod['category_parent_slug'] = $mod['category_parent_slug'] ?? null;
        $mod['author'] = $mod['author_slug'] ?: 'creator';
        $mod['author_name'] = $mod['author_name'] ?: 'Creator';
        $mod['author_avatar'] = $mod['author_avatar'] ?? null;
        $mod['creator_username'] = $mod['creator_username'] ?? $mod['author'];
        $mod['downloads'] = $mod['download_count'];
        $mod['views'] = $mod['view_count'];
        $mod['premium'] = $isPremium;
        $mod['is_premium'] = $isPremium;
        $mod['summary'] = $mod['summary'] ?: '';
        $mod['description'] = $mod['description'] ?: $mod['summary'];
        $mod['external_link'] = $mod['external_link'] ?: ($mod['file_url'] ?? '#');
        $mod['external_link_status'] = $mod['external_link_status'] ?: 'unchecked';
        $mod['external_link_risk_score'] = (int) ($mod['external_link_risk_score'] ?? 0);

        return $mod;
    }
}
