use axum::extract::{Path, Query, State};
use axum::http::StatusCode;
use axum::Json;
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;

use crate::db;
use crate::state::AppState;

#[derive(Deserialize)]
pub struct UserQuery {
    pub user_id: String,
}

fn format_time(t: OffsetDateTime) -> String {
    t.format(&time::format_description::well_known::Rfc3339)
        .unwrap_or_else(|_| t.to_string())
}

// --- Bot types ---

#[derive(Serialize)]
pub struct BotResponse {
    pub id: String,
    pub name: String,
    pub nick: String,
    pub network_addr: String,
    pub sasl_username: Option<String>,
    pub channels: Vec<String>,
    pub enabled: bool,
    pub status: String,
    pub status_message: Option<String>,
    pub running: bool,
    pub created_at: String,
    pub updated_at: String,
}

#[derive(Deserialize)]
pub struct CreateBotRequest {
    pub user_id: String,
    pub plan: String,
    pub name: String,
    pub nick: String,
    pub network_addr: String,
    pub sasl_username: Option<String>,
    pub sasl_password: Option<String>,
    pub channels: Vec<String>,
}

#[derive(Deserialize)]
pub struct UpdateBotRequest {
    pub user_id: String,
    pub name: String,
    pub nick: String,
    pub network_addr: String,
    pub sasl_username: Option<String>,
    pub sasl_password: Option<String>,
    pub channels: Vec<String>,
}

// --- Script types ---

#[derive(Serialize)]
pub struct ScriptResponse {
    pub id: String,
    pub bot_id: String,
    pub name: String,
    pub source: String,
    pub enabled: bool,
    pub created_at: String,
    pub updated_at: String,
}

#[derive(Deserialize)]
pub struct CreateScriptRequest {
    pub user_id: String,
    pub name: String,
    pub source: String,
}

#[derive(Deserialize)]
pub struct UpdateScriptRequest {
    pub user_id: String,
    pub name: String,
    pub source: String,
    pub enabled: bool,
}

// --- Log types ---

#[derive(Serialize)]
pub struct LogResponse {
    pub id: i64,
    pub level: String,
    pub message: String,
    pub created_at: String,
}

// --- KV types ---

#[derive(Serialize)]
pub struct KvResponse {
    pub key: String,
    pub value: String,
    pub updated_at: String,
}

#[derive(Deserialize)]
pub struct SetKvRequest {
    pub user_id: String,
    pub value: String,
}

// --- Usage types ---

#[derive(Serialize)]
pub struct UsageResponse {
    pub bot_count: i64,
    pub bots_enabled: i64,
    pub bots_running: i64,
}

// --- Error helper ---

#[derive(Serialize)]
pub struct ErrorBody {
    error: String,
}

fn error_json(status: StatusCode, msg: &str) -> (StatusCode, Json<ErrorBody>) {
    (
        status,
        Json(ErrorBody {
            error: msg.to_string(),
        }),
    )
}

// --- Conversions ---

async fn bot_to_response(state: &AppState, bot: db::Bot) -> BotResponse {
    let running = state.bot_manager.is_running(&bot.id).await;
    BotResponse {
        id: bot.id,
        name: bot.name,
        nick: bot.nick,
        network_addr: bot.network_addr,
        sasl_username: bot.sasl_username,
        channels: bot.channels,
        enabled: bot.enabled,
        status: bot.status,
        status_message: bot.status_message,
        running,
        created_at: format_time(bot.created_at),
        updated_at: format_time(bot.updated_at),
    }
}

fn script_to_response(s: db::BotScript) -> ScriptResponse {
    ScriptResponse {
        id: s.id,
        bot_id: s.bot_id,
        name: s.name,
        source: s.source,
        enabled: s.enabled,
        created_at: format_time(s.created_at),
        updated_at: format_time(s.updated_at),
    }
}

fn log_to_response(l: db::BotLog) -> LogResponse {
    LogResponse {
        id: l.id,
        level: l.level,
        message: l.message,
        created_at: format_time(l.created_at),
    }
}

fn kv_to_response(kv: db::BotKv) -> KvResponse {
    KvResponse {
        key: kv.key,
        value: kv.value,
        updated_at: format_time(kv.updated_at),
    }
}

// --- Bot endpoints ---

pub async fn list_bots(
    State(state): State<AppState>,
    Query(q): Query<UserQuery>,
) -> Result<Json<Vec<BotResponse>>, (StatusCode, Json<ErrorBody>)> {
    let bots = db::list_bots(&state.db, &q.user_id)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?;

    let mut out = Vec::with_capacity(bots.len());
    for bot in bots {
        out.push(bot_to_response(&state, bot).await);
    }
    Ok(Json(out))
}

pub async fn create_bot(
    State(state): State<AppState>,
    Json(req): Json<CreateBotRequest>,
) -> Result<(StatusCode, Json<BotResponse>), (StatusCode, Json<ErrorBody>)> {
    let limit: i64 = match req.plan.as_str() {
        "pro" => 5,
        _ => 1,
    };

    let count = db::count_bots(&state.db, &req.user_id)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?;

    if count >= limit {
        return Err(error_json(
            StatusCode::FORBIDDEN,
            &format!("bot limit reached ({limit})"),
        ));
    }

    let id = nanoid::nanoid!(12);

    db::insert_bot(
        &state.db,
        &id,
        &req.user_id,
        &req.name,
        &req.nick,
        &req.network_addr,
        req.sasl_username.as_deref(),
        req.sasl_password.as_deref(),
        &req.channels,
    )
    .await
    .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?;

    let bot = db::get_bot(&state.db, &id)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?
        .ok_or_else(|| error_json(StatusCode::INTERNAL_SERVER_ERROR, "bot not found after insert"))?;

    let resp = bot_to_response(&state, bot).await;
    Ok((StatusCode::CREATED, Json(resp)))
}

pub async fn get_bot(
    State(state): State<AppState>,
    Path(id): Path<String>,
    Query(q): Query<UserQuery>,
) -> Result<Json<BotResponse>, (StatusCode, Json<ErrorBody>)> {
    let bot = db::get_bot_owned(&state.db, &id, &q.user_id)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?
        .ok_or_else(|| error_json(StatusCode::NOT_FOUND, "bot not found"))?;

    Ok(Json(bot_to_response(&state, bot).await))
}

pub async fn update_bot(
    State(state): State<AppState>,
    Path(id): Path<String>,
    Json(req): Json<UpdateBotRequest>,
) -> Result<Json<BotResponse>, (StatusCode, Json<ErrorBody>)> {
    let updated = db::update_bot(
        &state.db,
        &id,
        &req.user_id,
        &req.name,
        &req.nick,
        &req.network_addr,
        req.sasl_username.as_deref(),
        req.sasl_password.as_deref(),
        &req.channels,
    )
    .await
    .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?;

    if !updated {
        return Err(error_json(StatusCode::NOT_FOUND, "bot not found"));
    }

    let bot = db::get_bot_owned(&state.db, &id, &req.user_id)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?
        .ok_or_else(|| error_json(StatusCode::NOT_FOUND, "bot not found"))?;

    Ok(Json(bot_to_response(&state, bot).await))
}

pub async fn delete_bot(
    State(state): State<AppState>,
    Path(id): Path<String>,
    Query(q): Query<UserQuery>,
) -> Result<StatusCode, (StatusCode, Json<ErrorBody>)> {
    let _bot = db::get_bot_owned(&state.db, &id, &q.user_id)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?
        .ok_or_else(|| error_json(StatusCode::NOT_FOUND, "bot not found"))?;

    if state.bot_manager.is_running(&id).await {
        let _ = state.bot_manager.stop_bot(&id).await;
    }

    let _ = db::delete_bot(&state.db, &id, &q.user_id).await;

    Ok(StatusCode::NO_CONTENT)
}

pub async fn start_bot(
    State(state): State<AppState>,
    Path(id): Path<String>,
    Query(q): Query<UserQuery>,
) -> Result<Json<BotResponse>, (StatusCode, Json<ErrorBody>)> {
    let bot = db::get_bot_owned(&state.db, &id, &q.user_id)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?
        .ok_or_else(|| error_json(StatusCode::NOT_FOUND, "bot not found"))?;

    state
        .bot_manager
        .start_bot(&bot.id)
        .await
        .map_err(|e| error_json(StatusCode::CONFLICT, &e))?;

    db::set_bot_status(&state.db, &id, "connecting", None)
        .await
        .ok();

    let bot = db::get_bot_owned(&state.db, &id, &q.user_id)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?
        .ok_or_else(|| error_json(StatusCode::NOT_FOUND, "bot not found"))?;

    Ok(Json(bot_to_response(&state, bot).await))
}

pub async fn stop_bot(
    State(state): State<AppState>,
    Path(id): Path<String>,
    Query(q): Query<UserQuery>,
) -> Result<Json<BotResponse>, (StatusCode, Json<ErrorBody>)> {
    let _bot = db::get_bot_owned(&state.db, &id, &q.user_id)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?
        .ok_or_else(|| error_json(StatusCode::NOT_FOUND, "bot not found"))?;

    state
        .bot_manager
        .stop_bot(&id)
        .await
        .map_err(|e| error_json(StatusCode::CONFLICT, &e))?;

    let bot = db::get_bot_owned(&state.db, &id, &q.user_id)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?
        .ok_or_else(|| error_json(StatusCode::NOT_FOUND, "bot not found"))?;

    Ok(Json(bot_to_response(&state, bot).await))
}

// --- Script endpoints ---

pub async fn list_scripts(
    State(state): State<AppState>,
    Path(id): Path<String>,
    Query(q): Query<UserQuery>,
) -> Result<Json<Vec<ScriptResponse>>, (StatusCode, Json<ErrorBody>)> {
    let _bot = db::get_bot_owned(&state.db, &id, &q.user_id)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?
        .ok_or_else(|| error_json(StatusCode::NOT_FOUND, "bot not found"))?;

    let scripts = db::list_scripts(&state.db, &id)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?;

    Ok(Json(scripts.into_iter().map(script_to_response).collect()))
}

pub async fn create_script(
    State(state): State<AppState>,
    Path(id): Path<String>,
    Json(req): Json<CreateScriptRequest>,
) -> Result<(StatusCode, Json<ScriptResponse>), (StatusCode, Json<ErrorBody>)> {
    let _bot = db::get_bot_owned(&state.db, &id, &req.user_id)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?
        .ok_or_else(|| error_json(StatusCode::NOT_FOUND, "bot not found"))?;

    let sid = nanoid::nanoid!(12);

    db::insert_script(&state.db, &sid, &id, &req.name, &req.source)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?;

    let script = db::get_script(&state.db, &sid)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?
        .ok_or_else(|| {
            error_json(
                StatusCode::INTERNAL_SERVER_ERROR,
                "script not found after insert",
            )
        })?;

    Ok((StatusCode::CREATED, Json(script_to_response(script))))
}

pub async fn get_script(
    State(state): State<AppState>,
    Path((id, sid)): Path<(String, String)>,
    Query(q): Query<UserQuery>,
) -> Result<Json<ScriptResponse>, (StatusCode, Json<ErrorBody>)> {
    let _bot = db::get_bot_owned(&state.db, &id, &q.user_id)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?
        .ok_or_else(|| error_json(StatusCode::NOT_FOUND, "bot not found"))?;

    let script = db::get_script(&state.db, &sid)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?
        .ok_or_else(|| error_json(StatusCode::NOT_FOUND, "script not found"))?;

    if script.bot_id != id {
        return Err(error_json(StatusCode::NOT_FOUND, "script not found"));
    }

    Ok(Json(script_to_response(script)))
}

pub async fn update_script(
    State(state): State<AppState>,
    Path((id, sid)): Path<(String, String)>,
    Json(req): Json<UpdateScriptRequest>,
) -> Result<Json<ScriptResponse>, (StatusCode, Json<ErrorBody>)> {
    let _bot = db::get_bot_owned(&state.db, &id, &req.user_id)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?
        .ok_or_else(|| error_json(StatusCode::NOT_FOUND, "bot not found"))?;

    let existing = db::get_script(&state.db, &sid)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?
        .ok_or_else(|| error_json(StatusCode::NOT_FOUND, "script not found"))?;

    if existing.bot_id != id {
        return Err(error_json(StatusCode::NOT_FOUND, "script not found"));
    }

    db::update_script(&state.db, &sid, &req.name, &req.source, req.enabled)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?;

    let script = db::get_script(&state.db, &sid)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?
        .ok_or_else(|| error_json(StatusCode::NOT_FOUND, "script not found"))?;

    Ok(Json(script_to_response(script)))
}

pub async fn delete_script(
    State(state): State<AppState>,
    Path((id, sid)): Path<(String, String)>,
    Query(q): Query<UserQuery>,
) -> Result<StatusCode, (StatusCode, Json<ErrorBody>)> {
    let _bot = db::get_bot_owned(&state.db, &id, &q.user_id)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?
        .ok_or_else(|| error_json(StatusCode::NOT_FOUND, "bot not found"))?;

    let existing = db::get_script(&state.db, &sid)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?
        .ok_or_else(|| error_json(StatusCode::NOT_FOUND, "script not found"))?;

    if existing.bot_id != id {
        return Err(error_json(StatusCode::NOT_FOUND, "script not found"));
    }

    db::delete_script(&state.db, &sid)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?;

    Ok(StatusCode::NO_CONTENT)
}

// --- Log endpoints ---

pub async fn list_logs(
    State(state): State<AppState>,
    Path(id): Path<String>,
    Query(q): Query<UserQuery>,
) -> Result<Json<Vec<LogResponse>>, (StatusCode, Json<ErrorBody>)> {
    let _bot = db::get_bot_owned(&state.db, &id, &q.user_id)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?
        .ok_or_else(|| error_json(StatusCode::NOT_FOUND, "bot not found"))?;

    let logs = db::list_logs(&state.db, &id, 200)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?;

    Ok(Json(logs.into_iter().map(log_to_response).collect()))
}

// --- KV endpoints ---

pub async fn list_kv(
    State(state): State<AppState>,
    Path(id): Path<String>,
    Query(q): Query<UserQuery>,
) -> Result<Json<Vec<KvResponse>>, (StatusCode, Json<ErrorBody>)> {
    let _bot = db::get_bot_owned(&state.db, &id, &q.user_id)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?
        .ok_or_else(|| error_json(StatusCode::NOT_FOUND, "bot not found"))?;

    let kvs = db::kv_list(&state.db, &id)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?;

    Ok(Json(kvs.into_iter().map(kv_to_response).collect()))
}

pub async fn set_kv(
    State(state): State<AppState>,
    Path((id, key)): Path<(String, String)>,
    Json(req): Json<SetKvRequest>,
) -> Result<Json<KvResponse>, (StatusCode, Json<ErrorBody>)> {
    let _bot = db::get_bot_owned(&state.db, &id, &req.user_id)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?
        .ok_or_else(|| error_json(StatusCode::NOT_FOUND, "bot not found"))?;

    db::kv_set(&state.db, &id, &key, &req.value)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?;

    let kv = db::kv_get(&state.db, &id, &key)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?
        .ok_or_else(|| error_json(StatusCode::INTERNAL_SERVER_ERROR, "kv not found after set"))?;

    Ok(Json(kv_to_response(kv)))
}

pub async fn delete_kv(
    State(state): State<AppState>,
    Path((id, key)): Path<(String, String)>,
    Query(q): Query<UserQuery>,
) -> Result<StatusCode, (StatusCode, Json<ErrorBody>)> {
    let _bot = db::get_bot_owned(&state.db, &id, &q.user_id)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?
        .ok_or_else(|| error_json(StatusCode::NOT_FOUND, "bot not found"))?;

    let deleted = db::kv_delete(&state.db, &id, &key)
        .await
        .map_err(|e| error_json(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()))?;

    if !deleted {
        return Err(error_json(StatusCode::NOT_FOUND, "key not found"));
    }

    Ok(StatusCode::NO_CONTENT)
}

// --- Usage endpoint ---

pub async fn user_usage(
    State(state): State<AppState>,
    Path(user_id): Path<String>,
) -> Json<UsageResponse> {
    let bot_count = db::count_bots(&state.db, &user_id).await.unwrap_or(0);

    let bots_enabled = sqlx::query_as::<_, (i64,)>(
        "SELECT COUNT(*) FROM bots WHERE user_id = $1 AND enabled = true",
    )
    .bind(&user_id)
    .fetch_one(&state.db)
    .await
    .map(|r| r.0)
    .unwrap_or(0);

    let bots = db::list_bots(&state.db, &user_id).await.unwrap_or_default();
    let mut bots_running: i64 = 0;
    for bot in &bots {
        if state.bot_manager.is_running(&bot.id).await {
            bots_running += 1;
        }
    }

    Json(UsageResponse {
        bot_count,
        bots_enabled,
        bots_running,
    })
}
