use anyhow::Context;
use deadpool_diesel::postgres::Connection;
use diesel::{
    dsl::{auto_type, insert_into, AsSelect},
    pg::Pg,
    prelude::*,
    Connection as _,
};
use serde::Serialize;
use uuid::Uuid;

use crate::{
    app_error::AppError,
    schema::{channels, email_channels, slack_channels},
    teams::Team,
};

#[derive(Associations, Clone, Debug, Identifiable, Queryable, Selectable)]
#[diesel(belongs_to(Team))]
pub struct Channel {
    pub id: Uuid,
    pub team_id: Uuid,
    pub name: String,
    pub enable_by_default: bool,

    #[diesel(embed)]
    pub email_data: Option<EmailChannel>,
    #[diesel(embed)]
    pub slack_data: Option<SlackChannel>,
}

#[derive(
    Associations, Clone, Debug, Identifiable, Insertable, Queryable, Selectable, Serialize,
)]
#[diesel(belongs_to(Channel, foreign_key = id))]
pub struct EmailChannel {
    pub id: Uuid,
    pub recipient: String,
    #[serde(skip_serializing)]
    pub verification_code: String,
    pub verification_code_guesses: i32,
    pub verified: bool,
}

#[derive(Associations, Clone, Debug, Identifiable, Insertable, Queryable, Selectable)]
#[diesel(belongs_to(Channel, foreign_key = id))]
pub struct SlackChannel {
    pub id: Uuid,
    pub oauth_state: String,
    pub access_token: String,
    pub conversation_id: String,
}

impl Channel {
    #[auto_type(no_type_alias)]
    pub fn all() -> _ {
        let select: AsSelect<Channel, Pg> = Channel::as_select();
        channels::table
            .left_join(email_channels::table)
            .left_join(slack_channels::table)
            .select(select)
    }

    #[auto_type(no_type_alias)]
    pub fn with_id(channel_id: Uuid) -> _ {
        channels::id.eq(channel_id)
    }

    #[auto_type(no_type_alias)]
    pub fn with_team(team_id: Uuid) -> _ {
        channels::team_id.eq(team_id)
    }

    pub async fn create_email_channel(
        db_conn: &Connection,
        team_id: Uuid,
    ) -> Result<Self, AppError> {
        let id = Uuid::now_v7();
        db_conn
            .interact(move |conn| {
                conn.transaction(move |conn| {
                    insert_into(channels::table)
                        .values((
                            channels::id.eq(id.clone()),
                            channels::team_id.eq(team_id),
                            channels::name.eq("Untitled Email Channel"),
                        ))
                        .execute(conn)?;
                    insert_into(email_channels::table)
                        .values(email_channels::id.eq(id.clone()))
                        .execute(conn)?;
                    Self::all().filter(Self::with_id(id)).first(conn)
                })
            })
            .await
            .unwrap()
            .context("Failed to insert new EmailChannel.")
            .map_err(|err| err.into())
    }
}
