<?php
declare(strict_types=1);

namespace App\Models;

use PDO;

class Friendship extends BaseModel
{
    protected string $table = 'friendships';

    public function relationship(int $viewerId, int $targetId): array
    {
        if ($viewerId === $targetId) {
            return ['state' => 'self', 'friendship' => null];
        }

        if ($this->isBlocked($viewerId, $targetId)) {
            return ['state' => 'blocked_by_me', 'friendship' => null];
        }

        if ($this->isBlocked($targetId, $viewerId)) {
            return ['state' => 'blocked_me', 'friendship' => null];
        }

        $friendship = $this->findBetween($viewerId, $targetId);
        if (!$friendship || ($friendship['status'] ?? '') === 'removed') {
            return ['state' => 'none', 'friendship' => $friendship];
        }

        if (($friendship['status'] ?? '') === 'accepted') {
            return ['state' => 'friends', 'friendship' => $friendship];
        }

        if (($friendship['status'] ?? '') === 'pending') {
            return [
                'state' => (int) $friendship['requester_id'] === $viewerId ? 'pending_sent' : 'pending_received',
                'friendship' => $friendship,
            ];
        }

        return ['state' => (string) ($friendship['status'] ?? 'none'), 'friendship' => $friendship];
    }

    public function request(int $requesterId, int $receiverId): array
    {
        if ($requesterId === $receiverId) {
            return ['ok' => false, 'message' => 'Kendi hesabina arkadaslik istegi gonderemezsin.'];
        }

        if ($this->isBlockedBetween($requesterId, $receiverId)) {
            return ['ok' => false, 'message' => 'Bu kullanici ile etkilesim engellenmis.'];
        }

        $pdo = $this->db();
        $pdo->beginTransaction();

        try {
            $existing = $this->findBetween($requesterId, $receiverId, true);

            if ($existing && ($existing['status'] ?? '') === 'accepted') {
                $pdo->commit();
                return ['ok' => true, 'message' => 'Zaten arkadassiniz.', 'state' => 'friends', 'friendship' => $existing];
            }

            if ($existing && ($existing['status'] ?? '') === 'pending') {
                if ((int) $existing['requester_id'] === $receiverId) {
                    $accepted = $this->setStatus((int) $existing['id'], 'accepted');
                    $pdo->commit();
                    return ['ok' => true, 'message' => 'Ters yonlu istek kabul edildi.', 'state' => 'friends', 'friendship' => $accepted];
                }

                $pdo->commit();
                return ['ok' => true, 'message' => 'Istek zaten gonderildi.', 'state' => 'pending_sent', 'friendship' => $existing];
            }

            if ($existing) {
                $statement = $pdo->prepare(
                    'UPDATE friendships
                     SET requester_id = :requester_id,
                         receiver_id = :receiver_id,
                         status = "pending",
                         updated_at = NOW()
                     WHERE id = :id'
                );
                $statement->execute([
                    'requester_id' => $requesterId,
                    'receiver_id' => $receiverId,
                    'id' => (int) $existing['id'],
                ]);
                $friendship = $this->find((int) $existing['id']);
                $pdo->commit();
                return ['ok' => true, 'message' => 'Arkadaslik istegi gonderildi.', 'state' => 'pending_sent', 'friendship' => $friendship];
            }

            $statement = $pdo->prepare(
                'INSERT INTO friendships (requester_id, receiver_id, status, created_at, updated_at)
                 VALUES (:requester_id, :receiver_id, "pending", NOW(), NOW())'
            );
            $statement->execute([
                'requester_id' => $requesterId,
                'receiver_id' => $receiverId,
            ]);
            $friendship = $this->find((int) $pdo->lastInsertId());
            $pdo->commit();

            return ['ok' => true, 'message' => 'Arkadaslik istegi gonderildi.', 'state' => 'pending_sent', 'friendship' => $friendship];
        } catch (\Throwable $exception) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }

            throw $exception;
        }
    }

    public function accept(int $userId, int $friendshipId = 0, int $otherUserId = 0): ?array
    {
        $friendship = $friendshipId > 0 ? $this->find($friendshipId) : $this->findBetween($userId, $otherUserId);
        if (!$friendship || (int) $friendship['receiver_id'] !== $userId || ($friendship['status'] ?? '') !== 'pending') {
            return null;
        }

        return $this->setStatus((int) $friendship['id'], 'accepted');
    }

    public function reject(int $userId, int $friendshipId = 0, int $otherUserId = 0): ?array
    {
        $friendship = $friendshipId > 0 ? $this->find($friendshipId) : $this->findBetween($userId, $otherUserId);
        if (!$friendship || (int) $friendship['receiver_id'] !== $userId || ($friendship['status'] ?? '') !== 'pending') {
            return null;
        }

        return $this->setStatus((int) $friendship['id'], 'rejected');
    }

    public function remove(int $userId, int $otherUserId): bool
    {
        $friendship = $this->findBetween($userId, $otherUserId);
        if (!$friendship) {
            return false;
        }

        $this->setStatus((int) $friendship['id'], 'removed');
        return true;
    }

    public function block(int $blockerId, int $blockedId): void
    {
        if ($blockerId === $blockedId) {
            return;
        }

        $pdo = $this->db();
        $pdo->beginTransaction();

        try {
            $statement = $pdo->prepare(
                'INSERT INTO blocked_users (blocker_id, blocked_id, created_at)
                 VALUES (:blocker_id, :blocked_id, NOW())
                 ON DUPLICATE KEY UPDATE created_at = VALUES(created_at)'
            );
            $statement->execute(['blocker_id' => $blockerId, 'blocked_id' => $blockedId]);

            $update = $pdo->prepare(
                'UPDATE friendships
                 SET status = "removed", updated_at = NOW()
                 WHERE (requester_id = :a1 AND receiver_id = :b1)
                    OR (requester_id = :b2 AND receiver_id = :a2)'
            );
            $update->execute(['a1' => $blockerId, 'b1' => $blockedId, 'b2' => $blockedId, 'a2' => $blockerId]);

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

            throw $exception;
        }
    }

    public function unblock(int $blockerId, int $blockedId): bool
    {
        $statement = $this->db()->prepare('DELETE FROM blocked_users WHERE blocker_id = :blocker_id AND blocked_id = :blocked_id');
        $statement->execute(['blocker_id' => $blockerId, 'blocked_id' => $blockedId]);

        return $statement->rowCount() > 0;
    }

    public function listForUser(int $userId): array
    {
        return [
            'friends' => $this->friendRows($userId, 'accepted'),
            'incoming' => $this->incomingRows($userId),
            'outgoing' => $this->outgoingRows($userId),
            'blocked' => $this->blockedRows($userId),
        ];
    }

    public function isBlockedBetween(int $a, int $b): bool
    {
        return $this->isBlocked($a, $b) || $this->isBlocked($b, $a);
    }

    public function isBlocked(int $blockerId, int $blockedId): bool
    {
        $statement = $this->db()->prepare('SELECT 1 FROM blocked_users WHERE blocker_id = :blocker_id AND blocked_id = :blocked_id LIMIT 1');
        $statement->execute(['blocker_id' => $blockerId, 'blocked_id' => $blockedId]);

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

    public function findBetween(int $a, int $b, bool $forUpdate = false): ?array
    {
        if ($a <= 0 || $b <= 0) {
            return null;
        }

        $sql = 'SELECT * FROM friendships
                WHERE (requester_id = :a1 AND receiver_id = :b1)
                   OR (requester_id = :b2 AND receiver_id = :a2)
                ORDER BY FIELD(status, "accepted", "pending", "rejected", "removed"), updated_at DESC
                LIMIT 1';
        if ($forUpdate) {
            $sql .= ' FOR UPDATE';
        }

        $statement = $this->db()->prepare($sql);
        $statement->execute(['a1' => $a, 'b1' => $b, 'b2' => $b, 'a2' => $a]);
        $row = $statement->fetch();

        return $row ?: null;
    }

    private function setStatus(int $friendshipId, string $status): ?array
    {
        $statement = $this->db()->prepare('UPDATE friendships SET status = :status, updated_at = NOW() WHERE id = :id');
        $statement->execute(['status' => $status, 'id' => $friendshipId]);

        return $this->find($friendshipId);
    }

    private function friendRows(int $userId, string $status): array
    {
        $statement = $this->db()->prepare(
            'SELECT f.*,
                    u.id AS user_id,
                    u.username,
                    u.nickname,
                    COALESCE(up.display_name, p.display_name, u.nickname, u.username) AS display_name,
                    COALESCE(up.avatar, p.avatar_path) AS avatar
             FROM friendships f
             INNER JOIN users u ON u.id = IF(f.requester_id = :user_id_join, f.receiver_id, f.requester_id)
             LEFT JOIN user_profiles up ON up.user_id = u.id
             LEFT JOIN profiles p ON p.user_id = u.id
             WHERE (f.requester_id = :user_id_a OR f.receiver_id = :user_id_b)
               AND f.status = :status
             ORDER BY f.updated_at DESC'
        );
        $statement->execute([
            'user_id_join' => $userId,
            'user_id_a' => $userId,
            'user_id_b' => $userId,
            'status' => $status,
        ]);

        return $statement->fetchAll();
    }

    private function incomingRows(int $userId): array
    {
        return $this->requestRows($userId, 'receiver_id');
    }

    private function outgoingRows(int $userId): array
    {
        return $this->requestRows($userId, 'requester_id');
    }

    private function requestRows(int $userId, string $side): array
    {
        $otherColumn = $side === 'receiver_id' ? 'requester_id' : 'receiver_id';
        $statement = $this->db()->prepare(
            'SELECT f.*,
                    u.id AS user_id,
                    u.username,
                    u.nickname,
                    COALESCE(up.display_name, p.display_name, u.nickname, u.username) AS display_name,
                    COALESCE(up.avatar, p.avatar_path) AS avatar
             FROM friendships f
             INNER JOIN users u ON u.id = f.' . $otherColumn . '
             LEFT JOIN user_profiles up ON up.user_id = u.id
             LEFT JOIN profiles p ON p.user_id = u.id
             WHERE f.' . $side . ' = :user_id
               AND f.status = "pending"
             ORDER BY f.created_at DESC'
        );
        $statement->execute(['user_id' => $userId]);

        return $statement->fetchAll();
    }

    private function blockedRows(int $userId): array
    {
        $statement = $this->db()->prepare(
            'SELECT b.id,
                    b.blocked_id AS user_id,
                    b.created_at,
                    u.username,
                    u.nickname,
                    COALESCE(up.display_name, p.display_name, u.nickname, u.username) AS display_name,
                    COALESCE(up.avatar, p.avatar_path) AS avatar
             FROM blocked_users b
             INNER JOIN users u ON u.id = b.blocked_id
             LEFT JOIN user_profiles up ON up.user_id = u.id
             LEFT JOIN profiles p ON p.user_id = u.id
             WHERE b.blocker_id = :user_id
             ORDER BY b.created_at DESC'
        );
        $statement->execute(['user_id' => $userId]);

        return $statement->fetchAll();
    }
}
