//! Profile GraphQL mutations for TeamProfile and CustomerProfile

use async_graphql::{Context, InputObject, Object, Result};
use uuid::Uuid;

use crate::db::Database;
use crate::graphql::types::{CustomerProfileType, EntityStatusType, TeamProfileType, TeamRoleType};
use crate::models::{CustomerProfile, EntityStatus, EventType, TeamProfile, TeamRole};
use crate::services::EventPublisher;
use crate::auth::UserContext;

// ==================== TEAM PROFILE INPUTS ====================

/// Input for creating a team profile
/// The ID must be the Kratos identity UUID
#[derive(InputObject)]
pub struct CreateTeamProfileInput {
    /// The Kratos identity UUID - this becomes the profile ID
    pub kratos_identity_id: Uuid,
    pub first_name: String,
    pub last_name: String,
    pub email: String,
    pub phone: String,
    pub role: TeamRoleType,
    pub status: Option<EntityStatusType>,
    pub notes: String,
}

/// Input for updating a team profile
/// Note: The profile ID (Kratos identity UUID) cannot be changed
#[derive(InputObject)]
pub struct UpdateTeamProfileInput {
    pub first_name: Option<String>,
    pub last_name: Option<String>,
    pub email: Option<String>,
    pub phone: Option<String>,
    pub role: Option<TeamRoleType>,
    pub status: Option<EntityStatusType>,
    pub notes: Option<String>,
}

// ==================== CUSTOMER PROFILE INPUTS ====================

/// Input for creating a customer profile
/// The ID must be the Kratos identity UUID
#[derive(InputObject)]
pub struct CreateCustomerProfileInput {
    /// The Kratos identity UUID - this becomes the profile ID
    pub kratos_identity_id: Uuid,
    pub first_name: String,
    pub last_name: String,
    pub email: String,
    pub phone: String,
    pub status: Option<EntityStatusType>,
    pub notes: String,
}

/// Input for updating a customer profile
#[derive(InputObject)]
pub struct UpdateCustomerProfileInput {
    pub first_name: Option<String>,
    pub last_name: Option<String>,
    pub email: Option<String>,
    pub phone: Option<String>,
    pub status: Option<EntityStatusType>,
    pub notes: Option<String>,
}

#[derive(Default)]
pub struct ProfileMutation;

#[Object]
impl ProfileMutation {
    // ==================== TEAM PROFILE MUTATIONS ====================

    /// Create a new team profile linked to a Kratos identity
    async fn create_team_profile(
        &self,
        ctx: &Context<'_>,
        input: CreateTeamProfileInput,
    ) -> Result<TeamProfileType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // Check if profile already exists
        let existing: Option<(Uuid,)> = sqlx::query_as(
            "SELECT id FROM team_profiles WHERE id = $1"
        )
        .bind(input.kratos_identity_id)
        .fetch_optional(&*pool)
        .await?;

        if existing.is_some() {
            return Err(async_graphql::Error::new(
                "A team profile already exists for this Kratos identity"
            ));
        }

        let status = input.status.map(EntityStatus::from).unwrap_or(EntityStatus::Active);
        let role = TeamRole::from(input.role);

        let profile: TeamProfile = sqlx::query_as::<_, TeamProfile>(
            r#"
            INSERT INTO team_profiles (
                id, created_at, updated_at, first_name, last_name, phone, email, role, status, notes
            )
            VALUES ($1, NOW(), NOW(), $2, $3, NULLIF($4, ''), NULLIF($5, ''), $6, $7, NULLIF($8, ''))
            RETURNING id, created_at, updated_at, first_name, last_name, phone, email, role, status, notes
            "#,
        )
        .bind(input.kratos_identity_id)
        .bind(&input.first_name)
        .bind(&input.last_name)
        .bind(&input.phone)
        .bind(&input.email)
        .bind(role)
        .bind(status)
        .bind(&input.notes)
        .fetch_one(&*pool)
        .await?;

        // Publish event for audit trail
        if let Ok(user) = ctx.data::<UserContext>() {
            let _ = EventPublisher::publish(
                &pool,
                EventType::TeamProfileCreated,
                "team_profile",
                profile.base.id,
                Some(("team_profile", user.user_id)),
                None,
            )
            .await;
        }

        Ok(TeamProfileType::from(profile))
    }

    /// Update a team profile
    async fn update_team_profile(
        &self,
        ctx: &Context<'_>,
        id: Uuid,
        input: UpdateTeamProfileInput,
    ) -> Result<TeamProfileType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // Get old status and role for event tracking
        let old_values: Option<(EntityStatus, TeamRole)> = sqlx::query_as(
            "SELECT status, role FROM team_profiles WHERE id = $1",
        )
        .bind(id)
        .fetch_optional(&*pool)
        .await?;

        let new_status = input.status.map(EntityStatus::from);
        let new_role = input.role.map(TeamRole::from);

        // Empty string clears optional fields, NULL means don't update
        let profile: TeamProfile = sqlx::query_as::<_, TeamProfile>(
            r#"
            UPDATE team_profiles
            SET
                first_name = COALESCE(NULLIF($2, ''), first_name),
                last_name = COALESCE(NULLIF($3, ''), last_name),
                email = CASE WHEN $4 IS NULL THEN email ELSE NULLIF($4, '') END,
                phone = CASE WHEN $5 IS NULL THEN phone ELSE NULLIF($5, '') END,
                role = COALESCE($6, role),
                status = COALESCE($7, status),
                notes = CASE WHEN $8 IS NULL THEN notes ELSE NULLIF($8, '') END,
                updated_at = NOW()
            WHERE id = $1
            RETURNING id, created_at, updated_at, first_name, last_name, phone, email, role, status, notes
            "#,
        )
        .bind(id)
        .bind(&input.first_name)
        .bind(&input.last_name)
        .bind(&input.email)
        .bind(&input.phone)
        .bind(new_role)
        .bind(new_status)
        .bind(&input.notes)
        .fetch_one(&*pool)
        .await?;

        // Publish events for status/role changes
        if let Ok(user) = ctx.data::<UserContext>() {
            if let Some((old_status, old_role)) = old_values {
                // Status change event
                if let Some(new) = new_status {
                    if old_status != new {
                        let _ = EventPublisher::publish(
                            &pool,
                            EventType::TeamProfileStatusChanged,
                            "team_profile",
                            profile.base.id,
                            Some(("team_profile", user.user_id)),
                            Some(serde_json::json!({
                                "old_status": format!("{:?}", old_status),
                                "new_status": format!("{:?}", new)
                            })),
                        )
                        .await;
                    }
                }

                // Role change event
                if let Some(new) = new_role {
                    if old_role != new {
                        let _ = EventPublisher::publish(
                            &pool,
                            EventType::TeamProfileRoleChanged,
                            "team_profile",
                            profile.base.id,
                            Some(("team_profile", user.user_id)),
                            Some(serde_json::json!({
                                "old_role": format!("{:?}", old_role),
                                "new_role": format!("{:?}", new)
                            })),
                        )
                        .await;
                    }
                }
            }
        }

        Ok(TeamProfileType::from(profile))
    }

    /// Delete a team profile
    async fn delete_team_profile(&self, ctx: &Context<'_>, id: Uuid) -> Result<bool> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let result = sqlx::query("DELETE FROM team_profiles WHERE id = $1")
            .bind(id)
            .execute(&*pool)
            .await?;

        let deleted = result.rows_affected() > 0;

        // Publish event for audit trail
        if deleted {
            if let Ok(user) = ctx.data::<UserContext>() {
                let _ = EventPublisher::publish(
                    &pool,
                    EventType::TeamProfileDeleted,
                    "team_profile",
                    id,
                    Some(("team_profile", user.user_id)),
                    None,
                )
                .await;
            }
        }

        Ok(deleted)
    }

    // ==================== CUSTOMER PROFILE MUTATIONS ====================

    /// Create a new customer profile linked to a Kratos identity
    async fn create_customer_profile(
        &self,
        ctx: &Context<'_>,
        input: CreateCustomerProfileInput,
    ) -> Result<CustomerProfileType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // Check if profile already exists
        let existing: Option<(Uuid,)> = sqlx::query_as(
            "SELECT id FROM customer_profiles WHERE id = $1"
        )
        .bind(input.kratos_identity_id)
        .fetch_optional(&*pool)
        .await?;

        if existing.is_some() {
            return Err(async_graphql::Error::new(
                "A customer profile already exists for this Kratos identity"
            ));
        }

        let status = input.status.map(EntityStatus::from).unwrap_or(EntityStatus::Active);

        let profile: CustomerProfile = sqlx::query_as::<_, CustomerProfile>(
            r#"
            INSERT INTO customer_profiles (
                id, created_at, updated_at, first_name, last_name, phone, email, status, notes
            )
            VALUES ($1, NOW(), NOW(), $2, $3, NULLIF($4, ''), NULLIF($5, ''), $6, NULLIF($7, ''))
            RETURNING id, created_at, updated_at, first_name, last_name, phone, email, status, notes
            "#,
        )
        .bind(input.kratos_identity_id)
        .bind(&input.first_name)
        .bind(&input.last_name)
        .bind(&input.phone)
        .bind(&input.email)
        .bind(status)
        .bind(&input.notes)
        .fetch_one(&*pool)
        .await?;

        // Publish event for audit trail
        if let Ok(user) = ctx.data::<UserContext>() {
            let _ = EventPublisher::publish(
                &pool,
                EventType::CustomerProfileCreated,
                "customer_profile",
                profile.base.id,
                Some(("team_profile", user.user_id)),
                None,
            )
            .await;
        }

        Ok(CustomerProfileType::from(profile))
    }

    /// Update a customer profile
    async fn update_customer_profile(
        &self,
        ctx: &Context<'_>,
        id: Uuid,
        input: UpdateCustomerProfileInput,
    ) -> Result<CustomerProfileType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // Get old status for event tracking
        let old_status: Option<EntityStatus> = sqlx::query_scalar(
            "SELECT status FROM customer_profiles WHERE id = $1",
        )
        .bind(id)
        .fetch_optional(&*pool)
        .await?;

        let new_status = input.status.map(EntityStatus::from);

        // Empty string clears optional fields, NULL means don't update
        let profile: CustomerProfile = sqlx::query_as::<_, CustomerProfile>(
            r#"
            UPDATE customer_profiles
            SET
                first_name = COALESCE(NULLIF($2, ''), first_name),
                last_name = COALESCE(NULLIF($3, ''), last_name),
                email = CASE WHEN $4 IS NULL THEN email ELSE NULLIF($4, '') END,
                phone = CASE WHEN $5 IS NULL THEN phone ELSE NULLIF($5, '') END,
                status = COALESCE($6, status),
                notes = CASE WHEN $7 IS NULL THEN notes ELSE NULLIF($7, '') END,
                updated_at = NOW()
            WHERE id = $1
            RETURNING id, created_at, updated_at, first_name, last_name, phone, email, status, notes
            "#,
        )
        .bind(id)
        .bind(&input.first_name)
        .bind(&input.last_name)
        .bind(&input.email)
        .bind(&input.phone)
        .bind(new_status)
        .bind(&input.notes)
        .fetch_one(&*pool)
        .await?;

        // Publish event for status change
        if let Ok(user) = ctx.data::<UserContext>() {
            if let (Some(old), Some(new)) = (old_status, new_status) {
                if old != new {
                    let _ = EventPublisher::publish(
                        &pool,
                        EventType::CustomerProfileStatusChanged,
                        "customer_profile",
                        profile.base.id,
                        Some(("team_profile", user.user_id)),
                        Some(serde_json::json!({
                            "old_status": format!("{:?}", old),
                            "new_status": format!("{:?}", new)
                        })),
                    )
                    .await;
                }
            }
        }

        Ok(CustomerProfileType::from(profile))
    }

    /// Delete a customer profile
    async fn delete_customer_profile(&self, ctx: &Context<'_>, id: Uuid) -> Result<bool> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // First remove customer access associations
        sqlx::query("DELETE FROM customer_profile_access WHERE customer_profile_id = $1")
            .bind(id)
            .execute(&*pool)
            .await?;

        let result = sqlx::query("DELETE FROM customer_profiles WHERE id = $1")
            .bind(id)
            .execute(&*pool)
            .await?;

        let deleted = result.rows_affected() > 0;

        // Publish event for audit trail
        if deleted {
            if let Ok(user) = ctx.data::<UserContext>() {
                let _ = EventPublisher::publish(
                    &pool,
                    EventType::CustomerProfileDeleted,
                    "customer_profile",
                    id,
                    Some(("team_profile", user.user_id)),
                    None,
                )
                .await;
            }
        }

        Ok(deleted)
    }
}
