<?php
declare(strict_types=1);

namespace App\Models;

use PDO;

class User extends BaseModel
{
    protected string $table = 'users';

    public function findByIdentifier(string $identifier): ?array
    {
        $identifier = strtolower(trim($identifier));
        $statement = $this->db()->prepare(
            "SELECT * FROM {$this->table}
            WHERE (LOWER(email) = :email_identifier OR LOWER(username) = :username_identifier)
                AND status = 'active'
            LIMIT 1"
        );
        $statement->execute([
            'email_identifier' => $identifier,
            'username_identifier' => $identifier,
        ]);
        $row = $statement->fetch();

        return $row ?: null;
    }

    public function findByEmail(string $email): ?array
    {
        $statement = $this->db()->prepare("SELECT * FROM {$this->table} WHERE LOWER(email) = :email LIMIT 1");
        $statement->execute(['email' => strtolower(trim($email))]);
        $row = $statement->fetch();

        return $row ?: null;
    }

    public function findPublicById(int $id): ?array
    {
        $statement = $this->db()->prepare(
            'SELECT u.id,
                    u.username,
                    u.email,
                    u.nickname,
                    u.role,
                    u.status,
                    u.created_at,
                    COALESCE(up.display_name, p.display_name, u.nickname, u.username) AS display_name,
                    COALESCE(up.about, p.bio, "") AS about,
                    COALESCE(up.avatar, p.avatar_path) AS avatar,
                    COALESCE(up.cover_image, p.cover_path) AS cover_image,
                    up.instagram,
                    up.youtube,
                    COALESCE(up.website, p.website_url) AS website
             FROM users u
             LEFT JOIN user_profiles up ON up.user_id = u.id
             LEFT JOIN profiles p ON p.user_id = u.id
             WHERE u.id = :id AND u.status = "active"
             LIMIT 1'
        );
        $statement->execute(['id' => $id]);
        $row = $statement->fetch();

        return $row ?: null;
    }

    public function findPublicByNickname(string $nickname): ?array
    {
        $nickname = strtolower(trim($nickname));
        $statement = $this->db()->prepare(
            'SELECT u.id,
                    u.username,
                    u.email,
                    u.nickname,
                    u.role,
                    u.status,
                    u.created_at,
                    COALESCE(up.display_name, p.display_name, u.nickname, u.username) AS display_name,
                    COALESCE(up.about, p.bio, "") AS about,
                    COALESCE(up.avatar, p.avatar_path) AS avatar,
                    COALESCE(up.cover_image, p.cover_path) AS cover_image,
                    up.instagram,
                    up.youtube,
                    COALESCE(up.website, p.website_url) AS website
             FROM users u
             LEFT JOIN user_profiles up ON up.user_id = u.id
             LEFT JOIN profiles p ON p.user_id = u.id
             WHERE u.status = "active"
               AND (LOWER(u.username) = :username OR LOWER(u.nickname) = :nickname OR LOWER(p.nickname) = :profile_nickname)
             LIMIT 1'
        );
        $statement->execute([
            'username' => $nickname,
            'nickname' => $nickname,
            'profile_nickname' => $nickname,
        ]);
        $row = $statement->fetch();

        return $row ?: null;
    }

    public function searchPublic(string $query, ?int $excludeUserId = null, int $limit = 12): array
    {
        $query = trim($query);
        $limit = min(max($limit, 1), 30);

        if ($query === '') {
            return [];
        }

        $statement = $this->db()->prepare(
            'SELECT u.id,
                    u.username,
                    u.nickname,
                    u.role,
                    COALESCE(up.display_name, p.display_name, u.nickname, u.username) AS display_name,
                    COALESCE(up.about, p.bio, "") AS about,
                    COALESCE(up.avatar, p.avatar_path) AS avatar
             FROM users u
             LEFT JOIN user_profiles up ON up.user_id = u.id
             LEFT JOIN profiles p ON p.user_id = u.id
             WHERE u.status = "active"
               AND (:exclude_id = 0 OR u.id <> :exclude_id_check)
               AND (
                    LOWER(u.username) LIKE :query_username
                    OR LOWER(u.nickname) LIKE :query_nickname
                    OR LOWER(up.display_name) LIKE :query_display
                    OR LOWER(p.display_name) LIKE :query_legacy_display
               )
             ORDER BY u.username ASC
             LIMIT :limit'
        );
        $statement->bindValue('exclude_id', $excludeUserId ?? 0, PDO::PARAM_INT);
        $statement->bindValue('exclude_id_check', $excludeUserId ?? 0, PDO::PARAM_INT);
        $like = '%' . strtolower($query) . '%';
        $statement->bindValue('query_username', $like, PDO::PARAM_STR);
        $statement->bindValue('query_nickname', $like, PDO::PARAM_STR);
        $statement->bindValue('query_display', $like, PDO::PARAM_STR);
        $statement->bindValue('query_legacy_display', $like, PDO::PARAM_STR);
        $statement->bindValue('limit', $limit, PDO::PARAM_INT);
        $statement->execute();

        return $statement->fetchAll();
    }

    public function usernameExists(string $username): bool
    {
        $statement = $this->db()->prepare("SELECT 1 FROM {$this->table} WHERE LOWER(username) = :username LIMIT 1");
        $statement->execute(['username' => strtolower(trim($username))]);

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

    public function emailExists(string $email): bool
    {
        $statement = $this->db()->prepare("SELECT 1 FROM {$this->table} WHERE LOWER(email) = :email LIMIT 1");
        $statement->execute(['email' => strtolower(trim($email))]);

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

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

        if (!empty($filters['q'])) {
            $where[] = '(u.username LIKE :q_username OR u.email LIKE :q_email OR u.nickname LIKE :q_nickname OR up.display_name LIKE :q_display)';
            $like = '%' . trim((string) $filters['q']) . '%';
            $params['q_username'] = $like;
            $params['q_email'] = $like;
            $params['q_nickname'] = $like;
            $params['q_display'] = $like;
        }

        if (!empty($filters['role'])) {
            $where[] = 'u.role = :role';
            $params['role'] = (string) $filters['role'];
        }

        if (!empty($filters['status'])) {
            $where[] = 'u.status = :status';
            $params['status'] = (string) $filters['status'];
        }

        $sql = 'SELECT u.id,
                       u.username,
                       u.email,
                       u.nickname,
                       u.role,
                       u.status,
                       u.language,
                       u.email_verified_at,
                       u.last_login_at,
                       u.created_at,
                       COALESCE(up.display_name, u.nickname, u.username) AS display_name,
                       up.avatar,
                       (SELECT COUNT(*) FROM mods m WHERE COALESCE(m.user_id, m.creator_id) = u.id) AS mod_count,
                       (SELECT COUNT(*) FROM music_tracks mt WHERE mt.user_id = u.id) AS music_count,
                       (SELECT COUNT(*) FROM subscriptions s WHERE s.subscriber_id = u.id OR s.user_id = u.id) AS subscription_count
                FROM users u
                LEFT JOIN user_profiles up ON up.user_id = u.id';

        if ($where !== []) {
            $sql .= ' WHERE ' . implode(' AND ', $where);
        }

        $sql .= ' ORDER BY u.created_at DESC, u.id DESC LIMIT :limit';

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

        return $statement->fetchAll();
    }

    public function updateRoleByAdmin(int $userId, string $role, int $adminId): void
    {
        $allowed = ['user', 'creator', 'moderator', 'admin'];
        if (!in_array($role, $allowed, true)) {
            throw new \InvalidArgumentException('Rol gecersiz.');
        }

        if ($userId === $adminId && $role !== 'admin') {
            throw new \InvalidArgumentException('Kendi admin rolunu kaldiramazsin.');
        }

        $statement = $this->db()->prepare('UPDATE users SET role = :role, updated_at = NOW() WHERE id = :id');
        $statement->execute(['role' => $role, 'id' => $userId]);
    }

    public function updateStatusByAdmin(int $userId, string $status, int $adminId): void
    {
        $allowed = ['active', 'pending', 'suspended', 'deleted'];
        if (!in_array($status, $allowed, true)) {
            throw new \InvalidArgumentException('Durum gecersiz.');
        }

        if ($userId === $adminId && $status !== 'active') {
            throw new \InvalidArgumentException('Kendi hesabini pasife alamazsin.');
        }

        $statement = $this->db()->prepare('UPDATE users SET status = :status, updated_at = NOW() WHERE id = :id');
        $statement->execute(['status' => $status, 'id' => $userId]);
    }

    public function createWithProfile(array $data): int
    {
        $pdo = $this->db();
        $pdo->beginTransaction();

        try {
            $statement = $pdo->prepare(
                "INSERT INTO {$this->table}
                (username, email, password_hash, nickname, role, status, language)
                VALUES
                (:username, :email, :password_hash, :nickname, :role, :status, :language)"
            );
            $statement->execute([
                'username' => $data['username'],
                'email' => $data['email'],
                'password_hash' => $data['password_hash'],
                'nickname' => $data['nickname'],
                'role' => $data['role'] ?? 'user',
                'status' => $data['status'] ?? 'active',
                'language' => $data['language'] ?? 'tr',
            ]);

            $userId = (int) $pdo->lastInsertId();
            (new Profile($pdo))->createForUser($userId, [
                'display_name' => $data['nickname'] ?: $data['username'],
                'billing_email' => $data['email'],
            ]);

            $this->createLegacyProfile($pdo, $userId, $data);
            $pdo->commit();

            return $userId;
        } catch (\Throwable $exception) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }

            throw $exception;
        }
    }

    public function updatePassword(int $userId, string $passwordHash): void
    {
        $statement = $this->db()->prepare("UPDATE {$this->table} SET password_hash = :password_hash, updated_at = NOW() WHERE id = :id");
        $statement->execute([
            'id' => $userId,
            'password_hash' => $passwordHash,
        ]);
    }

    public function updateLanguage(int $userId, string $language): void
    {
        $statement = $this->db()->prepare("UPDATE {$this->table} SET language = :language, updated_at = NOW() WHERE id = :id");
        $statement->execute([
            'id' => $userId,
            'language' => $language,
        ]);
    }

    private function createLegacyProfile(PDO $pdo, int $userId, array $data): void
    {
        $statement = $pdo->prepare(
            'INSERT INTO profiles (user_id, display_name, nickname, bio)
            VALUES (:user_id, :display_name, :nickname, :bio)
            ON DUPLICATE KEY UPDATE
                display_name = VALUES(display_name),
                bio = VALUES(bio),
                updated_at = NOW()'
        );
        $statement->execute([
            'user_id' => $userId,
            'display_name' => $data['nickname'] ?: $data['username'],
            'nickname' => $data['username'],
            'bio' => '',
        ]);
    }
}
