//! Messaging GraphQL types
//!
//! Types for conversations, messages, participants, and read receipts.

use async_graphql::{ComplexObject, Context, Enum, Result, SimpleObject};
use chrono::{DateTime, Utc};
use serde_json::Value as JsonValue;
use uuid::Uuid;

use crate::auth::{ProfileType, UserContext};
use crate::db::Database;
use crate::models::{
    Conversation, ConversationParticipant, ConversationType, Message, MessageReadReceipt,
    TeamProfile, CustomerProfile,
};

use super::{TeamProfileType, CustomerProfileType};

// ==================== ENUMS ====================

/// Conversation type enum for GraphQL
#[derive(Enum, Copy, Clone, Eq, PartialEq)]
pub enum ConversationTypeEnum {
    Direct,
    Group,
    Support,
}

impl From<ConversationType> for ConversationTypeEnum {
    fn from(ct: ConversationType) -> Self {
        match ct {
            ConversationType::Direct => Self::Direct,
            ConversationType::Group => Self::Group,
            ConversationType::Support => Self::Support,
        }
    }
}

// ==================== CONVERSATION ====================

/// Message thread/conversation
#[derive(SimpleObject)]
#[graphql(complex)]
pub struct ConversationType_ {
    pub id: Uuid,
    pub subject: Option<String>,
    pub conversation_type: ConversationTypeEnum,
    /// Entity this conversation is about (e.g., "service", "project", "account")
    pub entity_type: Option<String>,
    pub entity_id: Option<Uuid>,
    /// Who created the conversation (polymorphic type)
    pub created_by_type: Option<String>,
    pub created_by_id: Option<Uuid>,
    pub last_message_at: Option<DateTime<Utc>>,
    pub is_archived: bool,
    pub metadata: Option<JsonValue>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

#[ComplexObject]
impl ConversationType_ {
    /// Participants in this conversation
    async fn participants(&self, ctx: &Context<'_>) -> Result<Vec<ConversationParticipantType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let participants: Vec<ConversationParticipant> = sqlx::query_as(
            r#"
            SELECT id, created_at, updated_at, conversation_id, participant_type, participant_id,
                   last_read_at, unread_count, is_muted, is_archived, joined_at
            FROM conversation_participants
            WHERE conversation_id = $1 AND is_archived = false
            ORDER BY joined_at ASC
            "#,
        )
        .bind(self.id)
        .fetch_all(&*pool)
        .await?;

        Ok(participants.into_iter().map(ConversationParticipantType::from).collect())
    }

    /// Messages in this conversation (most recent first)
    async fn messages(
        &self,
        ctx: &Context<'_>,
        #[graphql(default = 50)] limit: i32,
        #[graphql(default)] include_deleted: bool,
    ) -> Result<Vec<MessageType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let messages: Vec<Message> = if include_deleted {
            sqlx::query_as(
                r#"
                SELECT id, created_at, updated_at, conversation_id, author_type, author_id,
                       content, is_deleted, reply_to_id, attachments, is_system_message, metadata
                FROM messages
                WHERE conversation_id = $1
                ORDER BY created_at DESC
                LIMIT $2
                "#,
            )
            .bind(self.id)
            .bind(limit)
            .fetch_all(&*pool)
            .await?
        } else {
            sqlx::query_as(
                r#"
                SELECT id, created_at, updated_at, conversation_id, author_type, author_id,
                       content, is_deleted, reply_to_id, attachments, is_system_message, metadata
                FROM messages
                WHERE conversation_id = $1 AND is_deleted = false
                ORDER BY created_at DESC
                LIMIT $2
                "#,
            )
            .bind(self.id)
            .bind(limit)
            .fetch_all(&*pool)
            .await?
        };

        Ok(messages.into_iter().map(MessageType::from).collect())
    }

    /// Unread count for the current user
    async fn unread_count(&self, ctx: &Context<'_>) -> Result<i32> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // Get current user context - if not authenticated, return 0
        let user = match ctx.data::<UserContext>() {
            Ok(u) => u,
            Err(_) => return Ok(0),
        };

        let participant_type = match user.profile_type {
            ProfileType::Team => "team_profile",
            ProfileType::Customer => "customer_profile",
        };

        let count: Option<i32> = sqlx::query_scalar(
            r#"
            SELECT unread_count
            FROM conversation_participants
            WHERE conversation_id = $1 AND participant_type = $2 AND participant_id = $3
            "#,
        )
        .bind(self.id)
        .bind(participant_type)
        .bind(user.user_id)
        .fetch_optional(&*pool)
        .await?;

        Ok(count.unwrap_or(0))
    }

    /// The profile that created this conversation (polymorphic)
    async fn created_by_profile(&self, ctx: &Context<'_>) -> Result<Option<ProfileUnion>> {
        let Some(ref created_by_type) = self.created_by_type else {
            return Ok(None);
        };
        let Some(created_by_id) = self.created_by_id else {
            return Ok(None);
        };

        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        match created_by_type.as_str() {
            "team_profile" => {
                let profile: Option<TeamProfile> = sqlx::query_as(
                    r#"
                    SELECT id, created_at, updated_at, first_name, last_name, phone, email, role, status, notes
                    FROM team_profiles
                    WHERE id = $1
                    "#,
                )
                .bind(created_by_id)
                .fetch_optional(&*pool)
                .await?;

                Ok(profile.map(|p| ProfileUnion::TeamProfile(TeamProfileType::from(p))))
            }
            "customer_profile" => {
                let profile: Option<CustomerProfile> = sqlx::query_as(
                    r#"
                    SELECT id, created_at, updated_at, customer_id, first_name, last_name, email, phone, status, notes, is_primary
                    FROM customer_profiles
                    WHERE id = $1
                    "#,
                )
                .bind(created_by_id)
                .fetch_optional(&*pool)
                .await?;

                Ok(profile.map(|p| ProfileUnion::CustomerProfile(CustomerProfileType::from(p))))
            }
            _ => Ok(None),
        }
    }
}

impl From<Conversation> for ConversationType_ {
    fn from(c: Conversation) -> Self {
        Self {
            id: c.base.id,
            subject: c.subject,
            conversation_type: c.conversation_type.into(),
            entity_type: c.entity_type,
            entity_id: c.entity_id,
            created_by_type: c.created_by_type,
            created_by_id: c.created_by_id,
            last_message_at: c.last_message_at,
            is_archived: c.is_archived,
            metadata: c.metadata,
            created_at: c.base.created_at,
            updated_at: c.base.updated_at,
        }
    }
}

// ==================== CONVERSATION PARTICIPANT ====================

/// User participation in a conversation
#[derive(SimpleObject)]
#[graphql(complex)]
pub struct ConversationParticipantType {
    pub id: Uuid,
    pub conversation_id: Uuid,
    /// Participant type ("team_profile" or "customer_profile")
    pub participant_type: String,
    pub participant_id: Uuid,
    pub last_read_at: Option<DateTime<Utc>>,
    pub unread_count: i32,
    pub is_muted: bool,
    pub is_archived: bool,
    pub joined_at: DateTime<Utc>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

#[ComplexObject]
impl ConversationParticipantType {
    /// The participant's profile (polymorphic)
    async fn participant_profile(&self, ctx: &Context<'_>) -> Result<Option<ProfileUnion>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        match self.participant_type.as_str() {
            "team_profile" => {
                let profile: Option<TeamProfile> = sqlx::query_as(
                    r#"
                    SELECT id, created_at, updated_at, first_name, last_name, phone, email, role, status, notes
                    FROM team_profiles
                    WHERE id = $1
                    "#,
                )
                .bind(self.participant_id)
                .fetch_optional(&*pool)
                .await?;

                Ok(profile.map(|p| ProfileUnion::TeamProfile(TeamProfileType::from(p))))
            }
            "customer_profile" => {
                let profile: Option<CustomerProfile> = sqlx::query_as(
                    r#"
                    SELECT id, created_at, updated_at, customer_id, first_name, last_name, email, phone, status, notes, is_primary
                    FROM customer_profiles
                    WHERE id = $1
                    "#,
                )
                .bind(self.participant_id)
                .fetch_optional(&*pool)
                .await?;

                Ok(profile.map(|p| ProfileUnion::CustomerProfile(CustomerProfileType::from(p))))
            }
            _ => Ok(None),
        }
    }
}

impl From<ConversationParticipant> for ConversationParticipantType {
    fn from(p: ConversationParticipant) -> Self {
        Self {
            id: p.base.id,
            conversation_id: p.conversation_id,
            participant_type: p.participant_type,
            participant_id: p.participant_id,
            last_read_at: p.last_read_at,
            unread_count: p.unread_count,
            is_muted: p.is_muted,
            is_archived: p.is_archived,
            joined_at: p.joined_at,
            created_at: p.base.created_at,
            updated_at: p.base.updated_at,
        }
    }
}

// ==================== MESSAGE ====================

/// Individual message in a conversation
#[derive(SimpleObject)]
#[graphql(complex)]
pub struct MessageType {
    pub id: Uuid,
    pub conversation_id: Uuid,
    /// Author type ("team_profile" or "customer_profile")
    pub author_type: String,
    pub author_id: Uuid,
    pub content: String,
    pub is_deleted: bool,
    pub reply_to_id: Option<Uuid>,
    pub attachments: Option<JsonValue>,
    pub is_system_message: bool,
    pub metadata: Option<JsonValue>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

#[ComplexObject]
impl MessageType {
    /// The author's profile (polymorphic)
    async fn author_profile(&self, ctx: &Context<'_>) -> Result<Option<ProfileUnion>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        match self.author_type.as_str() {
            "team_profile" => {
                let profile: Option<TeamProfile> = sqlx::query_as(
                    r#"
                    SELECT id, created_at, updated_at, first_name, last_name, phone, email, role, status, notes
                    FROM team_profiles
                    WHERE id = $1
                    "#,
                )
                .bind(self.author_id)
                .fetch_optional(&*pool)
                .await?;

                Ok(profile.map(|p| ProfileUnion::TeamProfile(TeamProfileType::from(p))))
            }
            "customer_profile" => {
                let profile: Option<CustomerProfile> = sqlx::query_as(
                    r#"
                    SELECT id, created_at, updated_at, customer_id, first_name, last_name, email, phone, status, notes, is_primary
                    FROM customer_profiles
                    WHERE id = $1
                    "#,
                )
                .bind(self.author_id)
                .fetch_optional(&*pool)
                .await?;

                Ok(profile.map(|p| ProfileUnion::CustomerProfile(CustomerProfileType::from(p))))
            }
            _ => Ok(None),
        }
    }

    /// The message this is a reply to (if any)
    async fn reply_to(&self, ctx: &Context<'_>) -> Result<Option<MessageType>> {
        let Some(reply_to_id) = self.reply_to_id else {
            return Ok(None);
        };

        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let message: Option<Message> = sqlx::query_as(
            r#"
            SELECT id, created_at, updated_at, conversation_id, author_type, author_id,
                   content, is_deleted, reply_to_id, attachments, is_system_message, metadata
            FROM messages
            WHERE id = $1
            "#,
        )
        .bind(reply_to_id)
        .fetch_optional(&*pool)
        .await?;

        Ok(message.map(MessageType::from))
    }

    /// Read receipts for this message
    async fn read_receipts(&self, ctx: &Context<'_>) -> Result<Vec<MessageReadReceiptType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let receipts: Vec<MessageReadReceipt> = sqlx::query_as(
            r#"
            SELECT id, created_at, updated_at, message_id, reader_type, reader_id, read_at
            FROM message_read_receipts
            WHERE message_id = $1
            ORDER BY read_at ASC
            "#,
        )
        .bind(self.id)
        .fetch_all(&*pool)
        .await?;

        Ok(receipts.into_iter().map(MessageReadReceiptType::from).collect())
    }
}

impl From<Message> for MessageType {
    fn from(m: Message) -> Self {
        Self {
            id: m.base.id,
            conversation_id: m.conversation_id,
            author_type: m.author_type,
            author_id: m.author_id,
            content: m.content,
            is_deleted: m.is_deleted,
            reply_to_id: m.reply_to_id,
            attachments: m.attachments,
            is_system_message: m.is_system_message,
            metadata: m.metadata,
            created_at: m.base.created_at,
            updated_at: m.base.updated_at,
        }
    }
}

// ==================== MESSAGE READ RECEIPT ====================

/// Tracks when a message was read by a participant
#[derive(SimpleObject)]
#[graphql(complex)]
pub struct MessageReadReceiptType {
    pub id: Uuid,
    pub message_id: Uuid,
    /// Reader type ("team_profile" or "customer_profile")
    pub reader_type: String,
    pub reader_id: Uuid,
    pub read_at: DateTime<Utc>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

#[ComplexObject]
impl MessageReadReceiptType {
    /// The reader's profile (polymorphic)
    async fn reader_profile(&self, ctx: &Context<'_>) -> Result<Option<ProfileUnion>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        match self.reader_type.as_str() {
            "team_profile" => {
                let profile: Option<TeamProfile> = sqlx::query_as(
                    r#"
                    SELECT id, created_at, updated_at, first_name, last_name, phone, email, role, status, notes
                    FROM team_profiles
                    WHERE id = $1
                    "#,
                )
                .bind(self.reader_id)
                .fetch_optional(&*pool)
                .await?;

                Ok(profile.map(|p| ProfileUnion::TeamProfile(TeamProfileType::from(p))))
            }
            "customer_profile" => {
                let profile: Option<CustomerProfile> = sqlx::query_as(
                    r#"
                    SELECT id, created_at, updated_at, customer_id, first_name, last_name, email, phone, status, notes, is_primary
                    FROM customer_profiles
                    WHERE id = $1
                    "#,
                )
                .bind(self.reader_id)
                .fetch_optional(&*pool)
                .await?;

                Ok(profile.map(|p| ProfileUnion::CustomerProfile(CustomerProfileType::from(p))))
            }
            _ => Ok(None),
        }
    }
}

impl From<MessageReadReceipt> for MessageReadReceiptType {
    fn from(r: MessageReadReceipt) -> Self {
        Self {
            id: r.base.id,
            message_id: r.message_id,
            reader_type: r.reader_type,
            reader_id: r.reader_id,
            read_at: r.read_at,
            created_at: r.base.created_at,
            updated_at: r.base.updated_at,
        }
    }
}

// ==================== PROFILE UNION ====================

/// Union type for polymorphic profile references
#[derive(async_graphql::Union)]
pub enum ProfileUnion {
    TeamProfile(TeamProfileType),
    CustomerProfile(CustomerProfileType),
}
