use actix_web::{HttpResponse, Responder, Scope, delete, get, post, put, web};
use sea_orm::{
    ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set, TransactionTrait,
};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::app_data::AppData;
use crate::entity::sea_orm_active_enums::{RecurrenceType, TargetType};
use crate::entity::{recurrence_rule, reminder_offset, todo};
use crate::error::ApiError;
use crate::middleware::auth::AuthUser;
use crate::timer::WorkerCommand;

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RecurrenceRuleInput {
    pub r#type: String,
    pub interval: Option<i32>,
    pub by_weekday: Option<i32>,
    pub by_monthday: Option<i32>,
    pub timezone: Option<String>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OffsetInput {
    pub offset_minutes: i32,
    pub channel_inapp: Option<bool>,
    pub channel_bark: Option<bool>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TodoInput {
    pub title: String,
    pub description: Option<String>,
    pub due_at: chrono::DateTime<chrono::Utc>,
    pub recurrence_rule: Option<RecurrenceRuleInput>,
    pub offsets: Option<Vec<OffsetInput>>,
}

#[derive(Debug, Serialize)]
pub struct OkResponse {
    pub ok: bool,
}

#[get("")]
async fn list_todos(
    app_data: web::Data<AppData>,
    auth: AuthUser,
) -> Result<impl Responder, ApiError> {
    let items = todo::Entity::find()
        .filter(todo::Column::OwnerId.eq(auth.user_id))
        .order_by_asc(todo::Column::DueAt)
        .find_also_related(recurrence_rule::Entity)
        .all(&app_data.db)
        .await?;

    let todo_ids: Vec<Uuid> = items.iter().map(|(t, _)| t.id).collect();

    let offsets = reminder_offset::Entity::find()
        .filter(reminder_offset::Column::TargetType.eq(TargetType::Todo))
        .filter(reminder_offset::Column::TargetId.is_in(todo_ids))
        .all(&app_data.db)
        .await?;

    let result: Vec<serde_json::Value> = items
        .into_iter()
        .map(|(t, rule)| {
            let todo_offsets: Vec<_> = offsets
                .iter()
                .filter(|o| o.target_id == t.id)
                .cloned()
                .collect();
            serde_json::json!({
                "id": t.id,
                "ownerId": t.owner_id,
                "title": t.title,
                "description": t.description,
                "dueAt": t.due_at,
                "recurrenceRuleId": t.recurrence_rule_id,
                "createdAt": t.created_at,
                "updatedAt": t.updated_at,
                "checkInAt": t.check_in_at,
                "checkInCount": t.check_in_count,
                "isCheckedIn": t.is_checked_in,
                "recurrenceRule": rule,
                "offsets": todo_offsets,
            })
        })
        .collect();

    Ok(HttpResponse::Ok().json(result))
}

#[post("")]
async fn create_todo(
    app_data: web::Data<AppData>,
    auth: AuthUser,
    body: web::Json<TodoInput>,
) -> Result<impl Responder, ApiError> {
    if body.title.is_empty() {
        return Err(ApiError::BadRequest("Invalid payload".to_string()));
    }

    let now = chrono::Utc::now().fixed_offset();
    let body = body.into_inner();
    let user_id = auth.user_id;

    let result = app_data
        .db
        .transaction::<_, todo::Model, ApiError>(|txn| {
            Box::pin(async move {
                // Create recurrence rule if provided
                let rule_id = if let Some(rule_input) = body.recurrence_rule {
                    let rule = recurrence_rule::ActiveModel {
                        id: Set(Uuid::new_v4()),
                        r#type: Set(parse_recurrence_type(&rule_input.r#type)?),
                        interval: Set(rule_input.interval.unwrap_or(1)),
                        by_weekday: Set(rule_input.by_weekday),
                        by_monthday: Set(rule_input.by_monthday),
                        timezone: Set(rule_input.timezone.unwrap_or("Asia/Shanghai".to_string())),
                        created_at: Set(now),
                        updated_at: Set(now),
                    };
                    let created = rule.insert(txn).await?;
                    Some(created.id)
                } else {
                    None
                };

                // Create todo
                let new_todo = todo::ActiveModel {
                    id: Set(Uuid::new_v4()),
                    owner_id: Set(user_id),
                    title: Set(body.title),
                    description: Set(body.description),
                    due_at: Set(body.due_at.fixed_offset()),
                    recurrence_rule_id: Set(rule_id),
                    created_at: Set(now),
                    updated_at: Set(now),
                    check_in_at: Set(None),
                    check_in_count: Set(0),
                    is_checked_in: Set(false),
                };
                let created_todo = new_todo.insert(txn).await?;

                // Create offsets
                if let Some(offsets) = body.offsets {
                    for offset in offsets {
                        let new_offset = reminder_offset::ActiveModel {
                            id: Set(Uuid::new_v4()),
                            target_type: Set(TargetType::Todo),
                            target_id: Set(created_todo.id),
                            offset_minutes: Set(offset.offset_minutes),
                            channel_inapp: Set(offset.channel_inapp.unwrap_or(true)),
                            channel_bark: Set(offset.channel_bark.unwrap_or(false)),
                            created_at: Set(now),
                            bark_title: Set(None),
                            bark_subtitle: Set(None),
                            bark_body_markdown: Set(None),
                            bark_level: Set(None),
                            bark_icon: Set(None),
                        };
                        new_offset.insert(txn).await?;
                    }
                }

                Ok(created_todo)
            })
        })
        .await
        .map_err(|e| match e {
            sea_orm::TransactionError::Connection(e) => ApiError::Internal(e.to_string()),
            sea_orm::TransactionError::Transaction(e) => e,
        })?;

    // 触发通知生成
    let _ = app_data
        .send_worker_command(WorkerCommand::GenerateNotifications {
            target_type: TargetType::Todo,
            target_id: result.id,
        })
        .await;

    Ok(HttpResponse::Ok().json(result))
}

#[get("/{id}")]
async fn get_todo(
    app_data: web::Data<AppData>,
    auth: AuthUser,
    path: web::Path<Uuid>,
) -> Result<impl Responder, ApiError> {
    let id = path.into_inner();

    let (t, rule) = todo::Entity::find_by_id(id)
        .filter(todo::Column::OwnerId.eq(auth.user_id))
        .find_also_related(recurrence_rule::Entity)
        .one(&app_data.db)
        .await?
        .ok_or_else(|| ApiError::NotFound("Not found".to_string()))?;

    let offsets = reminder_offset::Entity::find()
        .filter(reminder_offset::Column::TargetType.eq(TargetType::Todo))
        .filter(reminder_offset::Column::TargetId.eq(t.id))
        .all(&app_data.db)
        .await?;

    let result = serde_json::json!({
        "id": t.id,
        "ownerId": t.owner_id,
        "title": t.title,
        "description": t.description,
        "dueAt": t.due_at,
        "recurrenceRuleId": t.recurrence_rule_id,
        "createdAt": t.created_at,
        "updatedAt": t.updated_at,
        "checkInAt": t.check_in_at,
        "checkInCount": t.check_in_count,
        "isCheckedIn": t.is_checked_in,
        "recurrenceRule": rule,
        "offsets": offsets,
    });

    Ok(HttpResponse::Ok().json(result))
}

#[put("/{id}")]
async fn update_todo(
    app_data: web::Data<AppData>,
    auth: AuthUser,
    path: web::Path<Uuid>,
    body: web::Json<TodoInput>,
) -> Result<impl Responder, ApiError> {
    let id = path.into_inner();

    if body.title.is_empty() {
        return Err(ApiError::BadRequest("Invalid payload".to_string()));
    }

    let now = chrono::Utc::now().fixed_offset();
    let body = body.into_inner();
    let user_id = auth.user_id;

    let result = app_data
        .db
        .transaction::<_, todo::Model, ApiError>(|txn| {
            Box::pin(async move {
                let existing = todo::Entity::find_by_id(id)
                    .filter(todo::Column::OwnerId.eq(user_id))
                    .one(txn)
                    .await?
                    .ok_or_else(|| ApiError::NotFound("Not found".to_string()))?;

                // Handle recurrence rule
                let mut rule_id = existing.recurrence_rule_id;
                if let Some(rule_input) = body.recurrence_rule {
                    if let Some(existing_rule_id) = rule_id {
                        // Update existing rule
                        let mut rule: recurrence_rule::ActiveModel =
                            recurrence_rule::Entity::find_by_id(existing_rule_id)
                                .one(txn)
                                .await?
                                .ok_or_else(|| ApiError::Internal("Rule not found".to_string()))?
                                .into();
                        rule.r#type = Set(parse_recurrence_type(&rule_input.r#type)?);
                        rule.interval = Set(rule_input.interval.unwrap_or(1));
                        rule.by_weekday = Set(rule_input.by_weekday);
                        rule.by_monthday = Set(rule_input.by_monthday);
                        rule.timezone =
                            Set(rule_input.timezone.unwrap_or("Asia/Shanghai".to_string()));
                        rule.updated_at = Set(now);
                        rule.update(txn).await?;
                    } else {
                        // Create new rule
                        let rule = recurrence_rule::ActiveModel {
                            id: Set(Uuid::new_v4()),
                            r#type: Set(parse_recurrence_type(&rule_input.r#type)?),
                            interval: Set(rule_input.interval.unwrap_or(1)),
                            by_weekday: Set(rule_input.by_weekday),
                            by_monthday: Set(rule_input.by_monthday),
                            timezone: Set(rule_input
                                .timezone
                                .unwrap_or("Asia/Shanghai".to_string())),
                            created_at: Set(now),
                            updated_at: Set(now),
                        };
                        let created = rule.insert(txn).await?;
                        rule_id = Some(created.id);
                    }
                } else if let Some(existing_rule_id) = rule_id {
                    // Delete existing rule
                    recurrence_rule::Entity::delete_by_id(existing_rule_id)
                        .exec(txn)
                        .await?;
                    rule_id = None;
                }

                // Delete existing offsets and create new ones
                reminder_offset::Entity::delete_many()
                    .filter(reminder_offset::Column::TargetType.eq(TargetType::Todo))
                    .filter(reminder_offset::Column::TargetId.eq(id))
                    .exec(txn)
                    .await?;

                if let Some(offsets) = body.offsets {
                    for offset in offsets {
                        let new_offset = reminder_offset::ActiveModel {
                            id: Set(Uuid::new_v4()),
                            target_type: Set(TargetType::Todo),
                            target_id: Set(id),
                            offset_minutes: Set(offset.offset_minutes),
                            channel_inapp: Set(offset.channel_inapp.unwrap_or(true)),
                            channel_bark: Set(offset.channel_bark.unwrap_or(false)),
                            created_at: Set(now),
                            bark_title: Set(None),
                            bark_subtitle: Set(None),
                            bark_body_markdown: Set(None),
                            bark_level: Set(None),
                            bark_icon: Set(None),
                        };
                        new_offset.insert(txn).await?;
                    }
                }

                // Update todo
                let mut active: todo::ActiveModel = existing.into();
                active.title = Set(body.title);
                active.description = Set(body.description);
                active.due_at = Set(body.due_at.fixed_offset());
                active.recurrence_rule_id = Set(rule_id);
                active.updated_at = Set(now);
                let updated = active.update(txn).await?;

                Ok(updated)
            })
        })
        .await
        .map_err(|e| match e {
            sea_orm::TransactionError::Connection(e) => ApiError::Internal(e.to_string()),
            sea_orm::TransactionError::Transaction(e) => e,
        })?;

    // 触发通知重新生成
    let _ = app_data
        .send_worker_command(WorkerCommand::GenerateNotifications {
            target_type: TargetType::Todo,
            target_id: result.id,
        })
        .await;

    Ok(HttpResponse::Ok().json(result))
}

#[post("/{id}/check-in")]
async fn check_in_todo(
    app_data: web::Data<AppData>,
    auth: AuthUser,
    path: web::Path<Uuid>,
) -> Result<impl Responder, ApiError> {
    let id = path.into_inner();

    // Find and verify ownership
    let existing = todo::Entity::find_by_id(id)
        .filter(todo::Column::OwnerId.eq(auth.user_id))
        .one(&app_data.db)
        .await?
        .ok_or_else(|| ApiError::NotFound("Not found".to_string()))?;

    let now = chrono::Utc::now().fixed_offset();

    // Update check-in fields
    let mut active: todo::ActiveModel = existing.clone().into();
    active.is_checked_in = Set(true);
    active.check_in_at = Set(Some(now));
    active.check_in_count = Set(existing.check_in_count + 1);
    active.updated_at = Set(now);
    let updated = active.update(&app_data.db).await?;

    Ok(HttpResponse::Ok().json(updated))
}

#[delete("/{id}")]
async fn delete_todo(
    app_data: web::Data<AppData>,
    auth: AuthUser,
    path: web::Path<Uuid>,
) -> Result<impl Responder, ApiError> {
    let id = path.into_inner();

    let result = todo::Entity::delete_many()
        .filter(todo::Column::Id.eq(id))
        .filter(todo::Column::OwnerId.eq(auth.user_id))
        .exec(&app_data.db)
        .await?;

    if result.rows_affected == 0 {
        return Err(ApiError::NotFound("Not found".to_string()));
    }

    // Delete offsets
    reminder_offset::Entity::delete_many()
        .filter(reminder_offset::Column::TargetType.eq(TargetType::Todo))
        .filter(reminder_offset::Column::TargetId.eq(id))
        .exec(&app_data.db)
        .await?;

    Ok(HttpResponse::Ok().json(OkResponse { ok: true }))
}

fn parse_recurrence_type(s: &str) -> Result<RecurrenceType, ApiError> {
    match s {
        "hourly" => Ok(RecurrenceType::Hourly),
        "daily" => Ok(RecurrenceType::Daily),
        "weekly" => Ok(RecurrenceType::Weekly),
        "monthly" => Ok(RecurrenceType::Monthly),
        "yearly" => Ok(RecurrenceType::Yearly),
        _ => Err(ApiError::BadRequest("Invalid recurrence type".to_string())),
    }
}

pub fn routes() -> Scope {
    web::scope("/api/todos")
        .service(list_todos)
        .service(create_todo)
        .service(get_todo)
        .service(update_todo)
        .service(check_in_todo)
        .service(delete_todo)
}
