use anyhow::Result;
use diesel::{
    dsl::{auto_type, insert_into, AsSelect, Eq},
    pg::Pg,
    prelude::*,
};
use uuid::Uuid;

use crate::{
    channels::Channel,
    schema::{channel_selections, channels, projects},
    teams::Team,
};

pub const DEFAULT_PROJECT_NAME: &str = "default";

/// A project maps approximately to an application service, and allows messages
/// to be directed to an adjustable set of output channels.
#[derive(Associations, Clone, Debug, Identifiable, Insertable, Queryable, Selectable)]
#[diesel(table_name = projects)]
#[diesel(belongs_to(Team))]
pub struct Project {
    pub id: Uuid,
    pub team_id: Uuid,
    pub name: String,
}

impl Project {
    pub fn insert_new<'a>(
        db_conn: &mut diesel::PgConnection,
        team_id: &'a Uuid,
        name: &'a str,
    ) -> Result<Self> {
        let default_channels = Channel::all()
            .filter(Channel::with_team(team_id))
            .filter(Channel::where_enabled_by_default())
            .load(db_conn)?;
        let id: Uuid = Uuid::now_v7();
        let project: Self = insert_into(projects::table)
            .values((
                projects::id.eq(id),
                projects::team_id.eq(team_id),
                projects::name.eq(name),
            ))
            .get_result(db_conn)?;
        for channel in default_channels {
            insert_into(channel_selections::table)
                .values((
                    channel_selections::project_id.eq(&project.id),
                    channel_selections::channel_id.eq(&channel.id),
                ))
                .execute(db_conn)?;
        }
        Ok(project)
    }

    #[auto_type(no_type_alias)]
    pub fn all() -> _ {
        let select: AsSelect<Project, Pg> = Project::as_select();
        projects::table.select(select)
    }

    #[auto_type(no_type_alias)]
    pub fn with_id<'a>(project_id: &'a Uuid) -> _ {
        projects::id.eq(project_id)
    }

    #[auto_type(no_type_alias)]
    pub fn with_team<'a>(team_id: &'a Uuid) -> _ {
        projects::team_id.eq(team_id)
    }

    #[auto_type(no_type_alias)]
    pub fn with_name<'a>(name: &'a str) -> _ {
        projects::name.eq(name)
    }

    #[auto_type(no_type_alias)]
    pub fn selected_channels(&self) -> _ {
        let select: AsSelect<Channel, Pg> = Channel::as_select();
        let project_filter: Eq<channel_selections::project_id, Uuid> =
            channel_selections::project_id.eq(self.id);
        channels::table
            .inner_join(channel_selections::table)
            .filter(project_filter)
            .select(select)
    }
}
