use crate::model_ext::{
    AliasSourceExt, AliasSuffixExt, CursorPaginationExt, EntityPrefixExt, MagIden, ModelPagination,
};
use crate::{model_ext::SelectColumnsExt, CalckeyDbError, CalckeyModel};
use chrono::{DateTime, Utc};
use ck::{drive_file, follow_request, following, user, user_profile};
use ext_model_migration::{IntoIden, SelectStatement};
use magnetar_sdk::types::SpanFilter;
use sea_orm::{
    ColumnTrait, DbErr, EntityTrait, FromQueryResult, Iden, JoinType, QueryFilter, QueryResult,
    QuerySelect,
};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserData {
    pub user: user::Model,
    pub profile: Option<user_profile::Model>,
    pub avatar: Option<drive_file::Model>,
    pub banner: Option<drive_file::Model>,
}

impl FromQueryResult for UserData {
    fn from_query_result(res: &QueryResult, prefix: &str) -> Result<Self, DbErr> {
        let prefix = if prefix.is_empty() {
            user::Entity.base_prefix()
        } else {
            MagIden::alias(prefix)
        };

        Ok(UserData {
            user: user::Model::from_query_result(res, &prefix.to_string())?,
            profile: user_profile::Model::from_query_result_optional(
                res,
                &prefix.join_str_as_str(PROFILE),
            )?,
            avatar: drive_file::Model::from_query_result_optional(
                res,
                &prefix.join_str_as_str(AVATAR),
            )?,
            banner: drive_file::Model::from_query_result_optional(
                res,
                &prefix.join_str_as_str(BANNER),
            )?,
        })
    }
}

impl ModelPagination for UserData {
    fn id(&self) -> &str {
        &self.user.id
    }

    fn time(&self) -> DateTime<Utc> {
        self.user.created_at.into()
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserFollowData {
    pub follow: following::Model,
    pub user: UserData,
}

impl FromQueryResult for UserFollowData {
    fn from_query_result(res: &QueryResult, prefix: &str) -> Result<Self, DbErr> {
        Ok(UserFollowData {
            user: UserData::from_query_result(res, prefix)?,
            follow: following::Model::from_query_result(res, prefix)?,
        })
    }
}

impl ModelPagination for UserFollowData {
    fn id(&self) -> &str {
        &self.follow.id
    }

    fn time(&self) -> DateTime<Utc> {
        self.follow.created_at.into()
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserFollowRequestData {
    pub follow_request: follow_request::Model,
    pub user: UserData,
}

impl FromQueryResult for UserFollowRequestData {
    fn from_query_result(res: &QueryResult, prefix: &str) -> Result<Self, DbErr> {
        Ok(UserFollowRequestData {
            user: UserData::from_query_result(res, prefix)?,
            follow_request: follow_request::Model::from_query_result(res, prefix)?,
        })
    }
}

impl ModelPagination for UserFollowRequestData {
    fn id(&self) -> &str {
        &self.follow_request.id
    }

    fn time(&self) -> DateTime<Utc> {
        self.follow_request.created_at.into()
    }
}

#[derive(Clone)]
pub struct UserResolveOptions {
    pub with_avatar: bool,
    pub with_banner: bool,
    pub with_profile: bool,
}

const PROFILE: &str = "profile.";
const AVATAR: &str = "avatar.";
const BANNER: &str = "banner.";

#[derive(Clone)]
pub struct UserResolver {
    db: CalckeyModel,
}

impl UserResolver {
    pub fn new(db: CalckeyModel) -> Self {
        Self { db }
    }

    pub fn resolve(
        &self,
        q: &mut SelectStatement,
        user_tbl: &MagIden,
        UserResolveOptions {
            with_avatar,
            with_banner,
            with_profile,
        }: &UserResolveOptions,
    ) {
        q.add_aliased_columns::<user::Entity>(user_tbl);

        if *with_profile {
            let profile_tbl = user_tbl.join_str(PROFILE);

            q.add_aliased_columns::<user_profile::Entity>(&profile_tbl);

            q.join_columns(
                JoinType::LeftJoin,
                user::Relation::UserProfile.with_from_alias(user_tbl),
                &profile_tbl,
            );
        }

        if *with_avatar {
            let avatar_tbl = user_tbl.join_str(AVATAR);

            q.add_aliased_columns::<drive_file::Entity>(&avatar_tbl);

            q.join_columns(
                JoinType::LeftJoin,
                user::Relation::DriveFile2.with_from_alias(user_tbl),
                &avatar_tbl,
            );
        }

        if *with_banner {
            let banner_tbl = user_tbl.join_str(BANNER);

            q.add_aliased_columns::<drive_file::Entity>(&banner_tbl);

            q.join_columns(
                JoinType::LeftJoin,
                user::Relation::DriveFile1.with_from_alias(user_tbl),
                &banner_tbl,
            );
        }
    }

    pub async fn get_follow_requests(
        &self,
        options: &UserResolveOptions,
        followee: &str,
        pagination: &SpanFilter,
        prev: &mut Option<SpanFilter>,
        next: &mut Option<SpanFilter>,
        limit: u64,
    ) -> Result<Vec<UserData>, CalckeyDbError> {
        let user_tbl = user::Entity.base_prefix();

        let mut select = follow_request::Entity::find().join_as(
            JoinType::InnerJoin,
            follow_request::Relation::User1.with_to_alias(&user_tbl),
            user_tbl.clone().into_iden(),
        );

        let query = QuerySelect::query(&mut select);
        self.resolve(query, &user_tbl, options);

        let followers = select
            .filter(follow_request::Column::FolloweeId.eq(followee))
            .get_paginated_model::<UserFollowRequestData, _, _>(
                &self.db.0,
                None,
                (
                    follow_request::Column::CreatedAt,
                    follow_request::Column::Id,
                ),
                pagination,
                prev,
                next,
                limit,
            )
            .await?
            .into_iter()
            .map(|u| u.user)
            .collect();

        Ok(followers)
    }

    pub async fn get_followees(
        &self,
        options: &UserResolveOptions,
        follower: &str,
        pagination: &SpanFilter,
        prev: &mut Option<SpanFilter>,
        next: &mut Option<SpanFilter>,
        limit: u64,
    ) -> Result<Vec<UserData>, CalckeyDbError> {
        let user_tbl = user::Entity.base_prefix();

        let mut select = following::Entity::find().join_as(
            JoinType::InnerJoin,
            following::Relation::User2.with_to_alias(&user_tbl),
            user_tbl.clone().into_iden(),
        );

        let query = QuerySelect::query(&mut select);
        self.resolve(query, &user_tbl, options);

        let followers = select
            .filter(following::Column::FollowerId.eq(follower))
            .get_paginated_model::<UserFollowData, _, _>(
                &self.db.0,
                None,
                (following::Column::CreatedAt, following::Column::Id),
                pagination,
                prev,
                next,
                limit,
            )
            .await?
            .into_iter()
            .map(|u| u.user)
            .collect();

        Ok(followers)
    }

    pub async fn get_followers(
        &self,
        options: &UserResolveOptions,
        followee: &str,
        pagination: &SpanFilter,
        prev: &mut Option<SpanFilter>,
        next: &mut Option<SpanFilter>,
        limit: u64,
    ) -> Result<Vec<UserData>, CalckeyDbError> {
        let user_tbl = user::Entity.base_prefix();

        let mut select = following::Entity::find().join_as(
            JoinType::InnerJoin,
            following::Relation::User1.with_to_alias(&user_tbl),
            user_tbl.clone().into_iden(),
        );

        let query = QuerySelect::query(&mut select);
        self.resolve(query, &user_tbl, options);

        let followers = select
            .filter(following::Column::FolloweeId.eq(followee))
            .get_paginated_model::<UserFollowData, _, _>(
                &self.db.0,
                None,
                (following::Column::CreatedAt, following::Column::Id),
                pagination,
                prev,
                next,
                limit,
            )
            .await?
            .into_iter()
            .map(|u| u.user)
            .collect();

        Ok(followers)
    }
}
