use std::sync::Arc;

use axum::{
    body::Body,
    extract::{Multipart, Path as AxumPath, State},
    http::{header, HeaderValue, StatusCode},
    response::{IntoResponse, Response},
    routing::{get, post},
    Json, Router,
};
use serde_json::{json, Value};
use tokio::fs;

use crate::{
    images::{
        convert_and_store_image, delete_image_asset, is_gif_manifest_name, list_images,
        NYAN_CAT_GIF,
    },
    monitor::{
        build_state_response, current_active_images, current_gifs_enabled, current_memes_enabled,
        current_specs_enabled, current_switch_time, load_monitor_json, save_monitor_json,
        set_custom_panels,
    },
    types::{ActivateRequest, AppState, DeleteRequest, ErrorResponse},
    ui::index_response,
};

pub(crate) fn router(state: Arc<AppState>) -> Router {
    Router::new()
        .route("/", get(index))
        .route("/healthz", get(healthz))
        .route("/api/state", get(api_state))
        .route("/api/upload", post(api_upload))
        .route("/api/panels/activate", post(api_activate))
        .route("/api/panels/disable", post(api_disable))
        .route("/api/images/delete", post(api_delete))
        .route("/api/assets/nyan-cat.gif", get(api_nyan_cat))
        .route("/api/images/{name}", get(api_image))
        .with_state(state)
}

fn error_response(status: StatusCode, message: impl Into<String>) -> Response {
    let body = Json(ErrorResponse {
        error: message.into(),
    });
    (status, body).into_response()
}

async fn index() -> Response {
    index_response()
}

async fn healthz(State(state): State<Arc<AppState>>) -> Json<Value> {
    let display = crate::display::read_display_status(&state.display_status);
    Json(json!({
        "ok": true,
        "nativeDisplay": display.native_enabled,
        "displayConnected": display.connected,
    }))
}

async fn api_state(State(state): State<Arc<AppState>>) -> Response {
    match build_state_response(&state).await {
        Ok(payload) => Json(payload).into_response(),
        Err(err) => error_response(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()),
    }
}

async fn api_upload(State(state): State<Arc<AppState>>, mut multipart: Multipart) -> Response {
    let mut stored = None;

    while let Ok(Some(field)) = multipart.next_field().await {
        if field.name() != Some("file") {
            continue;
        }

        let file_name = field
            .file_name()
            .map(ToString::to_string)
            .unwrap_or_else(|| "panel".into());
        let bytes = match field.bytes().await {
            Ok(bytes) => bytes,
            Err(err) => {
                return error_response(StatusCode::BAD_REQUEST, format!("Upload failed: {err}"));
            }
        };

        match convert_and_store_image(&state, &file_name, &bytes).await {
            Ok(name) => stored = Some(name),
            Err(err) => return error_response(StatusCode::BAD_REQUEST, err.to_string()),
        }
    }

    match stored {
        Some(name) => Json(json!({ "ok": true, "name": name })).into_response(),
        None => error_response(StatusCode::BAD_REQUEST, "No file field provided"),
    }
}

async fn api_activate(
    State(state): State<Arc<AppState>>,
    Json(payload): Json<ActivateRequest>,
) -> Response {
    let switch_time = payload.switch_time.unwrap_or(10).clamp(1, 600);
    let specs_enabled = payload.specs_enabled.unwrap_or(false);
    let available = match list_images(&state).await {
        Ok(items) => items,
        Err(err) => return error_response(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()),
    };

    let available_names: Vec<String> = available.into_iter().map(|item| item.name).collect();
    let mut valid = Vec::new();
    for name in payload.images {
        if available_names.iter().any(|candidate| candidate == &name) && !valid.contains(&name) {
            valid.push(name);
        }
    }

    let has_static_selection = valid.iter().any(|name| !is_gif_manifest_name(name));
    let has_gif_selection = valid.iter().any(|name| is_gif_manifest_name(name));
    let memes_enabled = payload.memes_enabled.unwrap_or(has_static_selection);
    let gifs_enabled = payload.gifs_enabled.unwrap_or(has_gif_selection);

    if !specs_enabled
        && !(memes_enabled && has_static_selection)
        && !(gifs_enabled && has_gif_selection)
    {
        return error_response(
            StatusCode::BAD_REQUEST,
            "Enable specs, memes with a still image, or GIFs with an animated image",
        );
    }

    let mut monitor = match load_monitor_json(&state).await {
        Ok(value) => value,
        Err(err) => return error_response(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()),
    };

    set_custom_panels(
        &mut monitor,
        switch_time,
        specs_enabled,
        memes_enabled,
        gifs_enabled,
        &valid,
    );

    if let Err(err) = save_monitor_json(&state, &monitor).await {
        return error_response(StatusCode::INTERNAL_SERVER_ERROR, err.to_string());
    }

    Json(json!({
        "ok": true,
        "switchTime": switch_time,
        "specsEnabled": specs_enabled,
        "memesEnabled": memes_enabled,
        "gifsEnabled": gifs_enabled,
        "activeImages": valid,
    }))
    .into_response()
}

async fn api_disable(State(state): State<Arc<AppState>>) -> Response {
    let mut monitor = match load_monitor_json(&state).await {
        Ok(value) => value,
        Err(err) => return error_response(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()),
    };
    let switch_time = current_switch_time(&monitor);
    let active_images = current_active_images(&monitor);

    set_custom_panels(
        &mut monitor,
        switch_time,
        false,
        false,
        false,
        &active_images,
    );

    if let Err(err) = save_monitor_json(&state, &monitor).await {
        return error_response(StatusCode::INTERNAL_SERVER_ERROR, err.to_string());
    }

    Json(json!({ "ok": true })).into_response()
}

async fn api_delete(
    State(state): State<Arc<AppState>>,
    Json(payload): Json<DeleteRequest>,
) -> Response {
    if payload.name.contains('/') || payload.name.contains('\\') {
        return error_response(StatusCode::BAD_REQUEST, "Invalid file name");
    }

    if let Err(err) = delete_image_asset(&state, &payload.name).await {
        return match err.downcast_ref::<std::io::Error>() {
            Some(io_err) if io_err.kind() == std::io::ErrorKind::NotFound => {
                error_response(StatusCode::NOT_FOUND, "Image not found")
            }
            _ => error_response(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()),
        };
    }

    let mut monitor = match load_monitor_json(&state).await {
        Ok(value) => value,
        Err(err) => return error_response(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()),
    };

    let mut active_images = current_active_images(&monitor);
    let switch_time = current_switch_time(&monitor);
    let specs_enabled = current_specs_enabled(&monitor);
    let memes_enabled = current_memes_enabled(&monitor);
    let gifs_enabled = current_gifs_enabled(&monitor);
    let before = active_images.len();
    active_images.retain(|name| name != &payload.name);
    if active_images.len() != before {
        set_custom_panels(
            &mut monitor,
            switch_time,
            specs_enabled,
            memes_enabled,
            gifs_enabled,
            &active_images,
        );
        if let Err(err) = save_monitor_json(&state, &monitor).await {
            return error_response(StatusCode::INTERNAL_SERVER_ERROR, err.to_string());
        }
    }

    Json(json!({ "ok": true })).into_response()
}

async fn api_image(
    AxumPath(name): AxumPath<String>,
    State(state): State<Arc<AppState>>,
) -> Response {
    if name.contains('/') || name.contains('\\') {
        return error_response(StatusCode::BAD_REQUEST, "Invalid file name");
    }

    let path = state.image_dir.join(&name);
    let bytes = match fs::read(&path).await {
        Ok(bytes) => bytes,
        Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
            return error_response(StatusCode::NOT_FOUND, "Image not found");
        }
        Err(err) => return error_response(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()),
    };

    let mime = mime_guess::from_path(&path).first_or_octet_stream();
    let mut response = Response::new(Body::from(bytes));
    response.headers_mut().insert(
        header::CONTENT_TYPE,
        HeaderValue::from_str(mime.as_ref())
            .unwrap_or(HeaderValue::from_static("application/octet-stream")),
    );
    response
}

async fn api_nyan_cat() -> Response {
    let mut response = Response::new(Body::from(NYAN_CAT_GIF));
    response
        .headers_mut()
        .insert(header::CONTENT_TYPE, HeaderValue::from_static("image/gif"));
    response.headers_mut().insert(
        header::CACHE_CONTROL,
        HeaderValue::from_static("public, max-age=86400"),
    );
    response
}
