use model::{
    repos::{repo::RepoModel, repo_history_name::RepoHistoryNameModel},
    users::user::UserModel,
    workspace::{
        wk_history_name::WkHistoryNameModel, wk_member::WkMemberModel,
        workspace::WorkspaceModel,
    },
};
use uuid::Uuid;

use crate::{
    AppGitState,
    errors::{GitError, GitResult},
};

#[derive(Clone, Debug)]
pub struct DbRepoStatus {
    pub repo: RepoModel,
    pub wk: WorkspaceModel,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MemberRole {
    Owner,
    Admin,
    Member,
}

impl MemberRole {
    pub fn can_write(self) -> bool {
        matches!(self, Self::Owner | Self::Admin)
    }
}

impl AppGitState {
    pub async fn repo(
        &self,
        wk_name: String,
        repo_name: String,
    ) -> GitResult<DbRepoStatus> {
        let wk = self.resolve_wk(&wk_name).await?;
        let repo = self.resolve_repo(wk.id, &repo_name).await?;

        Ok(DbRepoStatus { repo, wk })
    }

    pub async fn member_check(
        &self,
        status: &DbRepoStatus,
        user: &UserModel,
    ) -> GitResult<Option<MemberRole>> {
        self.member_check_by_user_id(status, user.id).await
    }

    pub async fn member_check_by_user_id(
        &self,
        status: &DbRepoStatus,
        user_id: Uuid,
    ) -> GitResult<Option<MemberRole>> {
        let member = db::sqlx::query_as::<_, WkMemberModel>(
            "SELECT wk, \"user\", owner, admin, join_at, leave_at \
             FROM wk_member \
             WHERE wk = $1 AND \"user\" = $2 AND leave_at IS NULL",
        )
        .bind(status.wk.id)
        .bind(user_id)
        .fetch_optional(self.db.reader())
        .await?;

        Ok(member.map(|member| {
            if member.owner {
                MemberRole::Owner
            } else if member.admin {
                MemberRole::Admin
            } else {
                MemberRole::Member
            }
        }))
    }

    async fn resolve_wk(&self, name: &str) -> GitResult<WorkspaceModel> {
        if let Some(wk) = db::sqlx::query_as::<_, WorkspaceModel>(
            "SELECT id, name, description, avatar_url, created_at \
             FROM workspace \
             WHERE name = $1",
        )
        .bind(&name)
        .fetch_optional(self.db.reader())
        .await?
        {
            return Ok(wk);
        }

        let Some(history) = db::sqlx::query_as::<_, WkHistoryNameModel>(
            "SELECT id, wk, name, changed_by, created_at \
             FROM wk_history_name \
             WHERE name = $1 \
             ORDER BY created_at DESC \
             LIMIT 1",
        )
        .bind(&name)
        .fetch_optional(self.db.reader())
        .await?
        else {
            return Err(GitError::RepoNotFound);
        };

        db::sqlx::query_as::<_, WorkspaceModel>(
            "SELECT id, name, description, avatar_url, created_at \
             FROM workspace \
             WHERE id = $1",
        )
        .bind(history.wk)
        .fetch_optional(self.db.reader())
        .await?
        .ok_or(GitError::RepoNotFound)
    }

    async fn resolve_repo(&self, wk: Uuid, name: &str) -> GitResult<RepoModel> {
        if let Some(repo) = db::sqlx::query_as::<_, RepoModel>(
            "SELECT id, wk, name, description, default_branch, visibility, size_bytes, \
                    is_archived, is_template, is_mirror, created_by, created_at, updated_at, deleted_at \
             FROM repo \
             WHERE wk = $1 AND name = $2 AND deleted_at IS NULL",
        )
        .bind(wk)
        .bind(&name)
        .fetch_optional(self.db.reader())
        .await?
        {
            return Ok(repo);
        }

        let Some(history) = db::sqlx::query_as::<_, RepoHistoryNameModel>(
            "SELECT h.id, h.repo, h.name, h.changed_by, h.created_at \
             FROM repo_history_name h \
             INNER JOIN repo r ON h.repo = r.id \
             WHERE h.name = $1 AND r.wk = $2 AND r.deleted_at IS NULL \
             ORDER BY h.created_at DESC \
             LIMIT 1",
        )
        .bind(&name)
        .bind(wk)
        .fetch_optional(self.db.reader())
        .await?
        else {
            return Err(GitError::RepoNotFound);
        };

        db::sqlx::query_as::<_, RepoModel>(
            "SELECT id, wk, name, description, default_branch, visibility, size_bytes, \
                    is_archived, is_template, is_mirror, created_by, created_at, updated_at, deleted_at \
             FROM repo \
             WHERE id = $1 AND wk = $2 AND deleted_at IS NULL",
        )
        .bind(history.repo)
        .bind(wk)
        .fetch_optional(self.db.reader())
        .await?
        .ok_or(GitError::RepoNotFound)
    }
}
