<?php

class Messaging {
    private $db;

    public function __construct($db) {
        $this->db = $db;
    }

    public function isParticipant($conversationId, $userId) {
        $stmt = $this->db->prepare(
            "SELECT COUNT(*) FROM conversation_participants WHERE conversation_id = :conversation_id AND user_id = :user_id"
        );
        $stmt->bindValue(':conversation_id', $conversationId, PDO::PARAM_INT);
        $stmt->bindValue(':user_id', $userId, PDO::PARAM_INT);
        $stmt->execute();
        return $stmt->fetchColumn() > 0;
    }

    public function getOrCreateDirectConversation($userA, $userB) {
        $userA = (int)$userA;
        $userB = (int)$userB;

        $stmt = $this->db->prepare(
            "SELECT cp1.conversation_id
             FROM conversation_participants cp1
             INNER JOIN conversation_participants cp2 ON cp1.conversation_id = cp2.conversation_id AND cp2.user_id = :user_b
             INNER JOIN conversations c ON c.conversation_id = cp1.conversation_id
             WHERE cp1.user_id = :user_a AND c.is_group = 0
             LIMIT 1"
        );
        $stmt->bindValue(':user_a', $userA, PDO::PARAM_INT);
        $stmt->bindValue(':user_b', $userB, PDO::PARAM_INT);
        $stmt->execute();
        $existingId = $stmt->fetchColumn();
        if ($existingId) {
            return (int)$existingId;
        }

        $this->db->beginTransaction();
        try {
            $stmt = $this->db->prepare(
                "INSERT INTO conversations (is_group, title, created_by, created_at) VALUES (0, NULL, :created_by, NOW())"
            );
            $stmt->bindValue(':created_by', $userA, PDO::PARAM_INT);
            $stmt->execute();
            $conversationId = (int)$this->db->lastInsertId();

            $this->addParticipant($conversationId, $userA);
            $this->addParticipant($conversationId, $userB);

            $this->db->commit();
            return $conversationId;
        } catch (Throwable $e) {
            $this->db->rollBack();
            throw $e;
        }
    }

    public function createGroupConversation($creatorId, $title, array $participantIds) {
        $creatorId = (int)$creatorId;
        $title = trim((string)$title);
        if ($title === '') {
            throw new InvalidArgumentException('Group conversations require a title.');
        }

        $participantIds = array_unique(array_map('intval', $participantIds));
        $participantIds = array_filter($participantIds, fn($id) => $id > 0 && $id !== $creatorId);

        if (count($participantIds) < 2) {
            throw new InvalidArgumentException('Group conversations require at least 2 other participants.');
        }

        $this->db->beginTransaction();
        try {
            $stmt = $this->db->prepare(
                "INSERT INTO conversations (is_group, title, created_by, created_at) VALUES (1, :title, :created_by, NOW())"
            );
            $stmt->bindValue(':title', $title);
            $stmt->bindValue(':created_by', $creatorId, PDO::PARAM_INT);
            $stmt->execute();
            $conversationId = (int)$this->db->lastInsertId();

            $this->addParticipant($conversationId, $creatorId);
            foreach ($participantIds as $participantId) {
                $this->addParticipant($conversationId, $participantId);
            }

            $this->db->commit();
            return $conversationId;
        } catch (Throwable $e) {
            $this->db->rollBack();
            throw $e;
        }
    }

    private function addParticipant($conversationId, $userId) {
        $stmt = $this->db->prepare(
            "INSERT INTO conversation_participants (conversation_id, user_id, joined_at) VALUES (:conversation_id, :user_id, NOW())"
        );
        $stmt->bindValue(':conversation_id', $conversationId, PDO::PARAM_INT);
        $stmt->bindValue(':user_id', $userId, PDO::PARAM_INT);
        $stmt->execute();
    }

    public function getConversationsForUser($userId) {
        $userId = (int)$userId;

        $stmt = $this->db->prepare(
            "SELECT c.conversation_id, c.is_group, c.title,
                    (SELECT CASE
                        WHEN m.attachment_kind = 'voice' THEN '\xF0\x9F\x8E\xA4 Voice message'
                        WHEN m.attachment_kind = 'file' THEN CONCAT('\xF0\x9F\x93\x8E ', m.attachment_name)
                        ELSE m.body
                     END
                     FROM messages m
                     LEFT JOIN message_deletions md ON md.message_id = m.message_id AND md.user_id = :self_id4
                     WHERE m.conversation_id = c.conversation_id AND md.id IS NULL
                     ORDER BY m.message_id DESC LIMIT 1) AS last_message,
                    (SELECT m.created_at
                     FROM messages m
                     LEFT JOIN message_deletions md ON md.message_id = m.message_id AND md.user_id = :self_id5
                     WHERE m.conversation_id = c.conversation_id AND md.id IS NULL
                     ORDER BY m.message_id DESC LIMIT 1) AS last_message_at,
                    (
                      SELECT COUNT(*) FROM messages m
                      LEFT JOIN message_deletions md ON md.message_id = m.message_id AND md.user_id = :self_id
                      WHERE m.conversation_id = c.conversation_id
                        AND m.sender_id != :self_id6
                        AND m.created_at > COALESCE(cp.last_read_at, '1970-01-01')
                        AND md.id IS NULL
                    ) AS unread_count,
                    (
                      SELECT GROUP_CONCAT(u.username SEPARATOR ', ')
                      FROM conversation_participants cp2
                      INNER JOIN users u ON u.user_id = cp2.user_id
                      WHERE cp2.conversation_id = c.conversation_id AND cp2.user_id != :self_id2
                    ) AS other_usernames
             FROM conversations c
             INNER JOIN conversation_participants cp ON cp.conversation_id = c.conversation_id AND cp.user_id = :self_id3
             ORDER BY last_message_at IS NULL, last_message_at DESC, c.created_at DESC"
        );
        $stmt->bindValue(':self_id', $userId, PDO::PARAM_INT);
        $stmt->bindValue(':self_id2', $userId, PDO::PARAM_INT);
        $stmt->bindValue(':self_id3', $userId, PDO::PARAM_INT);
        $stmt->bindValue(':self_id4', $userId, PDO::PARAM_INT);
        $stmt->bindValue(':self_id5', $userId, PDO::PARAM_INT);
        $stmt->bindValue(':self_id6', $userId, PDO::PARAM_INT);
        $stmt->execute();
        $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

        foreach ($rows as &$row) {
            $row['display_name'] = $row['is_group'] ? $row['title'] : ($row['other_usernames'] ?: '(no other participant)');
            $row['unread_count'] = (int)$row['unread_count'];
        }

        return $rows;
    }

    public function getMessages($conversationId, $afterId = 0, $viewerUserId = null) {
        $conversationId = (int)$conversationId;
        $afterId = (int)$afterId;

        $sql = "SELECT m.message_id, m.conversation_id, m.sender_id, m.body,
                       m.attachment_path, m.attachment_name, m.attachment_type, m.attachment_size, m.attachment_kind,
                       m.created_at, u.username AS sender_username
                FROM messages m
                INNER JOIN users u ON u.user_id = m.sender_id";
        if ($viewerUserId !== null) {
            $sql .= " LEFT JOIN message_deletions md ON md.message_id = m.message_id AND md.user_id = :viewer_id";
        }
        $sql .= " WHERE m.conversation_id = :conversation_id AND m.message_id > :after_id";
        if ($viewerUserId !== null) {
            $sql .= " AND md.id IS NULL";
        }
        $sql .= " ORDER BY m.message_id ASC";

        $stmt = $this->db->prepare($sql);
        $stmt->bindValue(':conversation_id', $conversationId, PDO::PARAM_INT);
        $stmt->bindValue(':after_id', $afterId, PDO::PARAM_INT);
        if ($viewerUserId !== null) {
            $stmt->bindValue(':viewer_id', (int)$viewerUserId, PDO::PARAM_INT);
        }
        $stmt->execute();
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    /** Hides a message from one participant's own view only ("delete for me"). Others still see it. */
    public function deleteMessageForUser($messageId, $userId) {
        $messageId = (int)$messageId;
        $userId = (int)$userId;

        $stmt = $this->db->prepare("SELECT conversation_id FROM messages WHERE message_id = :message_id");
        $stmt->bindValue(':message_id', $messageId, PDO::PARAM_INT);
        $stmt->execute();
        $conversationId = $stmt->fetchColumn();

        if (!$conversationId || !$this->isParticipant((int)$conversationId, $userId)) {
            throw new InvalidArgumentException('Message not found or you are not a participant of its conversation.');
        }

        $stmt = $this->db->prepare(
            "INSERT IGNORE INTO message_deletions (message_id, user_id, deleted_at) VALUES (:message_id, :user_id, NOW())"
        );
        $stmt->bindValue(':message_id', $messageId, PDO::PARAM_INT);
        $stmt->bindValue(':user_id', $userId, PDO::PARAM_INT);
        $stmt->execute();
    }

    /** Admin oversight: every conversation in the system, regardless of the admin's own participation. */
    public function getAllConversationsForAdmin() {
        $stmt = $this->db->prepare(
            "SELECT c.conversation_id, c.is_group, c.title,
                    (SELECT m.body FROM messages m WHERE m.conversation_id = c.conversation_id ORDER BY m.message_id DESC LIMIT 1) AS last_message,
                    (SELECT m.created_at FROM messages m WHERE m.conversation_id = c.conversation_id ORDER BY m.message_id DESC LIMIT 1) AS last_message_at,
                    (SELECT COUNT(*) FROM messages m WHERE m.conversation_id = c.conversation_id) AS message_count,
                    (
                      SELECT GROUP_CONCAT(u.username SEPARATOR ', ')
                      FROM conversation_participants cp
                      INNER JOIN users u ON u.user_id = cp.user_id
                      WHERE cp.conversation_id = c.conversation_id
                    ) AS participant_usernames
             FROM conversations c
             ORDER BY last_message_at IS NULL, last_message_at DESC, c.created_at DESC"
        );
        $stmt->execute();
        $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

        foreach ($rows as &$row) {
            $row['display_name'] = $row['is_group'] ? $row['title'] : ($row['participant_usernames'] ?: '(no participants)');
        }

        return $rows;
    }

    /** Admin oversight: every message in a conversation, bypassing participant membership and per-user deletions. */
    public function getAllMessagesForAdmin($conversationId) {
        $conversationId = (int)$conversationId;

        $stmt = $this->db->prepare(
            "SELECT m.message_id, m.conversation_id, m.sender_id, m.body,
                    m.attachment_path, m.attachment_name, m.attachment_type, m.attachment_size, m.attachment_kind,
                    m.created_at, u.username AS sender_username,
                    (
                      SELECT GROUP_CONCAT(du.username SEPARATOR ', ')
                      FROM message_deletions md
                      INNER JOIN users du ON du.user_id = md.user_id
                      WHERE md.message_id = m.message_id
                    ) AS deleted_for_usernames
             FROM messages m
             INNER JOIN users u ON u.user_id = m.sender_id
             WHERE m.conversation_id = :conversation_id
             ORDER BY m.message_id ASC"
        );
        $stmt->bindValue(':conversation_id', $conversationId, PDO::PARAM_INT);
        $stmt->execute();
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    public function sendMessage($conversationId, $senderId, $body, ?array $attachment = null) {
        $body = trim((string)$body);
        if ($body === '' && !$attachment) {
            throw new InvalidArgumentException('Message body cannot be empty.');
        }

        $stmt = $this->db->prepare(
            "INSERT INTO messages (conversation_id, sender_id, body, attachment_path, attachment_name, attachment_type, attachment_size, attachment_kind, created_at)
             VALUES (:conversation_id, :sender_id, :body, :attachment_path, :attachment_name, :attachment_type, :attachment_size, :attachment_kind, NOW())"
        );
        $stmt->bindValue(':conversation_id', $conversationId, PDO::PARAM_INT);
        $stmt->bindValue(':sender_id', $senderId, PDO::PARAM_INT);
        $stmt->bindValue(':body', $body);
        $stmt->bindValue(':attachment_path', $attachment['path'] ?? null);
        $stmt->bindValue(':attachment_name', $attachment['name'] ?? null);
        $stmt->bindValue(':attachment_type', $attachment['type'] ?? null);
        $stmt->bindValue(':attachment_size', $attachment['size'] ?? null, $attachment ? PDO::PARAM_INT : PDO::PARAM_NULL);
        $stmt->bindValue(':attachment_kind', $attachment['kind'] ?? null);
        $stmt->execute();
        $messageId = (int)$this->db->lastInsertId();

        $this->markRead($conversationId, $senderId);

        $stmt = $this->db->prepare(
            "SELECT m.message_id, m.conversation_id, m.sender_id, m.body,
                    m.attachment_path, m.attachment_name, m.attachment_type, m.attachment_size, m.attachment_kind,
                    m.created_at, u.username AS sender_username
             FROM messages m
             INNER JOIN users u ON u.user_id = m.sender_id
             WHERE m.message_id = :message_id"
        );
        $stmt->bindValue(':message_id', $messageId, PDO::PARAM_INT);
        $stmt->execute();
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }

    public function markRead($conversationId, $userId) {
        $stmt = $this->db->prepare(
            "UPDATE conversation_participants SET last_read_at = NOW() WHERE conversation_id = :conversation_id AND user_id = :user_id"
        );
        $stmt->bindValue(':conversation_id', $conversationId, PDO::PARAM_INT);
        $stmt->bindValue(':user_id', $userId, PDO::PARAM_INT);
        $stmt->execute();
    }

    public function getUnreadCount($userId) {
        $userId = (int)$userId;

        $stmt = $this->db->prepare(
            "SELECT COUNT(*) FROM messages m
             INNER JOIN conversation_participants cp ON cp.conversation_id = m.conversation_id AND cp.user_id = :self_id
             WHERE m.sender_id != :self_id2
               AND m.created_at > COALESCE(cp.last_read_at, '1970-01-01')"
        );
        $stmt->bindValue(':self_id', $userId, PDO::PARAM_INT);
        $stmt->bindValue(':self_id2', $userId, PDO::PARAM_INT);
        $stmt->execute();
        return (int)$stmt->fetchColumn();
    }

    public function searchUsers($query, $excludeUserId) {
        $query = trim((string)$query);
        $stmt = $this->db->prepare(
            "SELECT user_id, username, role FROM users WHERE username LIKE :query AND user_id != :exclude_id ORDER BY username LIMIT 20"
        );
        $stmt->bindValue(':query', '%' . $query . '%');
        $stmt->bindValue(':exclude_id', $excludeUserId, PDO::PARAM_INT);
        $stmt->execute();
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
}
