use crate::AppService;
use crate::error::AppError;
use chrono::Utc;
use models::projects::{project_board, project_board_card, project_board_column};
use sea_orm::*;
use serde::{Deserialize, Serialize};
use session::Session;
use uuid::Uuid;

#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct CreateBoardParams {
    pub name: String,
    pub description: Option<String>,
}

#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct UpdateBoardParams {
    pub name: Option<String>,
    pub description: Option<String>,
}

#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
pub struct BoardResponse {
    pub id: Uuid,
    pub project: Uuid,
    pub name: String,
    pub description: Option<String>,
    pub created_by: Uuid,
    pub created_at: chrono::DateTime<Utc>,
    pub updated_at: chrono::DateTime<Utc>,
}

impl From<project_board::Model> for BoardResponse {
    fn from(m: project_board::Model) -> Self {
        Self {
            id: m.id,
            project: m.project,
            name: m.name,
            description: m.description,
            created_by: m.created_by,
            created_at: m.created_at,
            updated_at: m.updated_at,
        }
    }
}

#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct CreateColumnParams {
    pub name: String,
    #[serde(default)]
    pub position: i32,
    pub wip_limit: Option<i32>,
    pub color: Option<String>,
}

#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct UpdateColumnParams {
    pub name: Option<String>,
    pub position: Option<i32>,
    pub wip_limit: Option<i32>,
    pub color: Option<String>,
}

#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
pub struct ColumnResponse {
    pub id: Uuid,
    pub board: Uuid,
    pub name: String,
    pub position: i32,
    pub wip_limit: Option<i32>,
    pub color: Option<String>,
}

impl From<project_board_column::Model> for ColumnResponse {
    fn from(m: project_board_column::Model) -> Self {
        Self {
            id: m.id,
            board: m.board,
            name: m.name,
            position: m.position,
            wip_limit: m.wip_limit,
            color: m.color,
        }
    }
}

#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct CreateCardParams {
    pub column_id: Uuid,
    pub title: String,
    pub description: Option<String>,
    pub issue_id: Option<i64>,
    pub assignee_id: Option<Uuid>,
    pub due_date: Option<chrono::DateTime<Utc>>,
    pub priority: Option<String>,
}

#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct UpdateCardParams {
    pub title: Option<String>,
    pub description: Option<String>,
    pub assignee_id: Option<Uuid>,
    pub due_date: Option<chrono::DateTime<Utc>>,
    pub priority: Option<String>,
}

#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct MoveCardParams {
    pub target_column_id: Uuid,
    pub position: i32,
}

#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
pub struct CardResponse {
    pub id: Uuid,
    pub column: Uuid,
    pub issue_id: Option<i64>,
    pub project: Option<Uuid>,
    pub title: String,
    pub description: Option<String>,
    pub position: i32,
    pub assignee_id: Option<Uuid>,
    pub due_date: Option<chrono::DateTime<Utc>>,
    pub priority: Option<String>,
    pub created_by: Uuid,
    pub created_at: chrono::DateTime<Utc>,
    pub updated_at: chrono::DateTime<Utc>,
}

impl From<project_board_card::Model> for CardResponse {
    fn from(m: project_board_card::Model) -> Self {
        Self {
            id: m.id,
            column: m.column,
            issue_id: m.issue_id,
            project: m.project,
            title: m.title,
            description: m.description,
            position: m.position,
            assignee_id: m.assignee_id,
            due_date: m.due_date,
            priority: m.priority,
            created_by: m.created_by,
            created_at: m.created_at,
            updated_at: m.updated_at,
        }
    }
}

#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
pub struct BoardWithColumnsResponse {
    pub board: BoardResponse,
    pub columns: Vec<ColumnWithCardsResponse>,
}

#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
pub struct ColumnWithCardsResponse {
    pub column: ColumnResponse,
    pub cards: Vec<CardResponse>,
}

impl AppService {
    /// List all boards for a project.
    pub async fn board_list(
        &self,
        project_name: String,
        ctx: &Session,
    ) -> Result<Vec<BoardResponse>, AppError> {
        let _ = ctx.user().ok_or(AppError::Unauthorized)?;
        let project = self.utils_find_project_by_name(project_name).await?;
        let boards = project_board::Entity::find()
            .filter(project_board::Column::Project.eq(project.id))
            .order_by_asc(project_board::Column::CreatedAt)
            .all(&self.db)
            .await?;
        Ok(boards.into_iter().map(BoardResponse::from).collect())
    }

    pub async fn board_get(
        &self,
        project_name: String,
        board_id: Uuid,
        ctx: &Session,
    ) -> Result<BoardWithColumnsResponse, AppError> {
        let _ = ctx.user().ok_or(AppError::Unauthorized)?;
        let project = self.utils_find_project_by_name(project_name).await?;

        let board = project_board::Entity::find_by_id(board_id)
            .filter(project_board::Column::Project.eq(project.id))
            .one(&self.db)
            .await?
            .ok_or_else(|| AppError::NotFound("Board not found".to_string()))?;

        let columns = project_board_column::Entity::find()
            .filter(project_board_column::Column::Board.eq(board_id))
            .order_by_asc(project_board_column::Column::Position)
            .all(&self.db)
            .await?;

        let column_ids: Vec<Uuid> = columns.iter().map(|c| c.id).collect();
        let cards = if column_ids.is_empty() {
            vec![]
        } else {
            project_board_card::Entity::find()
                .filter(project_board_card::Column::Column.is_in(column_ids))
                .order_by_asc(project_board_card::Column::Position)
                .all(&self.db)
                .await?
        };

        let columns_with_cards: Vec<ColumnWithCardsResponse> = columns
            .into_iter()
            .map(|c| {
                let col_cards: Vec<CardResponse> = cards
                    .iter()
                    .filter(|card| card.column == c.id)
                    .cloned()
                    .map(CardResponse::from)
                    .collect();
                ColumnWithCardsResponse {
                    column: ColumnResponse::from(c),
                    cards: col_cards,
                }
            })
            .collect();

        Ok(BoardWithColumnsResponse {
            board: BoardResponse::from(board),
            columns: columns_with_cards,
        })
    }

    /// Create a new board.
    pub async fn board_create(
        &self,
        project_name: String,
        params: CreateBoardParams,
        ctx: &Session,
    ) -> Result<BoardResponse, AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let project = self.utils_find_project_by_name(project_name).await?;
        let now = Utc::now();

        let active = project_board::ActiveModel {
            id: Set(Uuid::new_v4()),
            project: Set(project.id),
            name: Set(params.name),
            description: Set(params.description),
            created_by: Set(user_uid),
            created_at: Set(now),
            updated_at: Set(now),
        };
        let board = active.insert(&self.db).await?;
        Ok(BoardResponse::from(board))
    }

    /// Update a board.
    pub async fn board_update(
        &self,
        project_name: String,
        board_id: Uuid,
        params: UpdateBoardParams,
        ctx: &Session,
    ) -> Result<BoardResponse, AppError> {
        let _ = ctx.user().ok_or(AppError::Unauthorized)?;
        let project = self.utils_find_project_by_name(project_name).await?;

        let board = project_board::Entity::find_by_id(board_id)
            .filter(project_board::Column::Project.eq(project.id))
            .one(&self.db)
            .await?
            .ok_or_else(|| AppError::NotFound("Board not found".to_string()))?;

        let mut active: project_board::ActiveModel = board.into();
        if let Some(v) = params.name {
            active.name = Set(v);
        }
        if params.description.is_some() {
            active.description = Set(params.description);
        }
        active.updated_at = Set(Utc::now());
        let updated = active.update(&self.db).await?;
        Ok(BoardResponse::from(updated))
    }

    /// Delete a board.
    pub async fn board_delete(
        &self,
        project_name: String,
        board_id: Uuid,
        ctx: &Session,
    ) -> Result<(), AppError> {
        let _ = ctx.user().ok_or(AppError::Unauthorized)?;
        let project = self.utils_find_project_by_name(project_name).await?;

        let deleted = project_board::Entity::delete_by_id(board_id)
            .filter(project_board::Column::Project.eq(project.id))
            .exec(&self.db)
            .await?;
        if deleted.rows_affected == 0 {
            return Err(AppError::NotFound("Board not found".to_string()));
        }
        Ok(())
    }

    /// Create a column on a board.
    pub async fn column_create(
        &self,
        project_name: String,
        board_id: Uuid,
        params: CreateColumnParams,
        ctx: &Session,
    ) -> Result<ColumnResponse, AppError> {
        let _ = ctx.user().ok_or(AppError::Unauthorized)?;
        let project = self.utils_find_project_by_name(project_name).await?;

        let board = project_board::Entity::find_by_id(board_id)
            .filter(project_board::Column::Project.eq(project.id))
            .one(&self.db)
            .await?
            .ok_or_else(|| AppError::NotFound("Board not found".to_string()))?;

        let active = project_board_column::ActiveModel {
            id: Set(Uuid::new_v4()),
            board: Set(board.id),
            name: Set(params.name),
            position: Set(params.position),
            wip_limit: Set(params.wip_limit),
            color: Set(params.color),
        };
        let column = active.insert(&self.db).await?;
        Ok(ColumnResponse::from(column))
    }

    /// Update a column.
    pub async fn column_update(
        &self,
        project_name: String,
        column_id: Uuid,
        params: UpdateColumnParams,
        ctx: &Session,
    ) -> Result<ColumnResponse, AppError> {
        let _ = ctx.user().ok_or(AppError::Unauthorized)?;
        let _project = self.utils_find_project_by_name(project_name).await?;

        let column = project_board_column::Entity::find_by_id(column_id)
            .one(&self.db)
            .await?
            .ok_or_else(|| AppError::NotFound("Column not found".to_string()))?;

        let mut active: project_board_column::ActiveModel = column.into();
        if let Some(v) = params.name {
            active.name = Set(v);
        }
        if let Some(v) = params.position {
            active.position = Set(v);
        }
        if params.wip_limit.is_some() {
            active.wip_limit = Set(params.wip_limit);
        }
        if params.color.is_some() {
            active.color = Set(params.color);
        }
        let updated = active.update(&self.db).await?;
        Ok(ColumnResponse::from(updated))
    }

    /// Delete a column.
    pub async fn column_delete(
        &self,
        project_name: String,
        column_id: Uuid,
        ctx: &Session,
    ) -> Result<(), AppError> {
        let _ = ctx.user().ok_or(AppError::Unauthorized)?;
        let _project = self.utils_find_project_by_name(project_name).await?;

        let deleted = project_board_column::Entity::delete_by_id(column_id)
            .exec(&self.db)
            .await?;
        if deleted.rows_affected == 0 {
            return Err(AppError::NotFound("Column not found".to_string()));
        }
        Ok(())
    }

    /// Create a card in a column.
    pub async fn card_create(
        &self,
        project_name: String,
        params: CreateCardParams,
        ctx: &Session,
    ) -> Result<CardResponse, AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let project = self.utils_find_project_by_name(project_name).await?;
        let now = Utc::now();

        let active = project_board_card::ActiveModel {
            id: Set(Uuid::new_v4()),
            column: Set(params.column_id),
            issue_id: Set(params.issue_id),
            project: Set(Some(project.id)),
            title: Set(params.title),
            description: Set(params.description),
            position: Set(0),
            assignee_id: Set(params.assignee_id),
            due_date: Set(params.due_date),
            priority: Set(params.priority),
            created_by: Set(user_uid),
            created_at: Set(now),
            updated_at: Set(now),
        };
        let card = active.insert(&self.db).await?;
        Ok(CardResponse::from(card))
    }

    /// Update a card.
    pub async fn card_update(
        &self,
        project_name: String,
        card_id: Uuid,
        params: UpdateCardParams,
        ctx: &Session,
    ) -> Result<CardResponse, AppError> {
        let _ = ctx.user().ok_or(AppError::Unauthorized)?;
        let _project = self.utils_find_project_by_name(project_name).await?;

        let card = project_board_card::Entity::find_by_id(card_id)
            .one(&self.db)
            .await?
            .ok_or_else(|| AppError::NotFound("Card not found".to_string()))?;

        let mut active: project_board_card::ActiveModel = card.into();
        if let Some(v) = params.title {
            active.title = Set(v);
        }
        if params.description.is_some() {
            active.description = Set(params.description);
        }
        if params.assignee_id.is_some() {
            active.assignee_id = Set(params.assignee_id);
        }
        if params.due_date.is_some() {
            active.due_date = Set(params.due_date);
        }
        if params.priority.is_some() {
            active.priority = Set(params.priority);
        }
        active.updated_at = Set(Utc::now());
        let updated = active.update(&self.db).await?;
        Ok(CardResponse::from(updated))
    }

    /// Move a card to a different column and/or position.
    pub async fn card_move(
        &self,
        project_name: String,
        card_id: Uuid,
        params: MoveCardParams,
        ctx: &Session,
    ) -> Result<CardResponse, AppError> {
        let _ = ctx.user().ok_or(AppError::Unauthorized)?;
        let _project = self.utils_find_project_by_name(project_name).await?;

        let card = project_board_card::Entity::find_by_id(card_id)
            .one(&self.db)
            .await?
            .ok_or_else(|| AppError::NotFound("Card not found".to_string()))?;

        let mut active: project_board_card::ActiveModel = card.into();
        active.column = Set(params.target_column_id);
        active.position = Set(params.position);
        active.updated_at = Set(Utc::now());
        let updated = active.update(&self.db).await?;
        Ok(CardResponse::from(updated))
    }

    /// Delete a card.
    pub async fn card_delete(
        &self,
        project_name: String,
        card_id: Uuid,
        ctx: &Session,
    ) -> Result<(), AppError> {
        let _ = ctx.user().ok_or(AppError::Unauthorized)?;
        let _project = self.utils_find_project_by_name(project_name).await?;

        let deleted = project_board_card::Entity::delete_by_id(card_id)
            .exec(&self.db)
            .await?;
        if deleted.rows_affected == 0 {
            return Err(AppError::NotFound("Card not found".to_string()));
        }
        Ok(())
    }
}
