use axum::{
    extract::{FromRequestParts, Path},
    http::{header, request::Parts, StatusCode},
    response::{IntoResponse, Redirect, Response},
};

use crate::api::AppState;
use crate::registry::Role;
use crate::session::{self, SessionEntry, COOKIE_NAME};

/// Injected by the `AuthUser` extractor; available as a handler parameter.
#[derive(Clone)]
pub struct AuthUser(pub SessionEntry);

impl FromRequestParts<AppState> for AuthUser {
    type Rejection = Response;

    async fn from_request_parts(parts: &mut Parts, state: &AppState) -> Result<Self, Response> {
        let Path(slug) = Path::<String>::from_request_parts(parts, state)
            .await
            .map_err(|_| axum::http::StatusCode::BAD_REQUEST.into_response())?;

        let registry = &state.registry;
        let Some(ctx) = registry.get(&slug) else {
            return Err(axum::http::StatusCode::NOT_FOUND.into_response());
        };

        if !ctx.auth_enabled() {
            // Auth not required for this project — synthesise a pass-through entry.
            use std::time::{SystemTime, UNIX_EPOCH};
            let expires = SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap_or_default()
                .as_secs()
                + 86400;
            return Ok(AuthUser(SessionEntry {
                id: String::new(),
                slug,
                role: Role::Admin,
                key_label: String::new(),
                expires,
            }));
        }

        let cookie_str = parts
            .headers
            .get(header::COOKIE)
            .and_then(|v| v.to_str().ok())
            .unwrap_or("");

        let Some(token) = session::extract_cookie(cookie_str, COOKIE_NAME) else {
            return Err(Redirect::to(&format!("/ui/{slug}/login")).into_response());
        };

        let Some(entry) = state.sessions.get(&token) else {
            return Err(Redirect::to(&format!("/ui/{slug}/login")).into_response());
        };

        if entry.slug != slug {
            return Err(Redirect::to(&format!("/ui/{slug}/login")).into_response());
        }

        Ok(AuthUser(entry))
    }
}

/// Global manage-page guard — no slug in path.
///
/// Allows access when:
/// - The registry has no projects with auth enabled (open/loopback deployment).
/// - The request carries a valid admin-role session cookie for any project.
///
/// On failure redirects to the login page of the first alphabetically sorted
/// project that has auth enabled; returns 403 if there is no such project to
/// redirect to.
pub struct AdminGuard;

impl FromRequestParts<AppState> for AdminGuard {
    type Rejection = Response;

    async fn from_request_parts(parts: &mut Parts, state: &AppState) -> Result<Self, Response> {
        let registry = &state.registry;

        let auth_projects: Vec<String> = registry
            .all()
            .into_iter()
            .filter(|ctx| ctx.auth_enabled())
            .map(|ctx| ctx.slug.clone())
            .collect();

        if auth_projects.is_empty() {
            // No project requires auth — open deployment, pass through.
            return Ok(AdminGuard);
        }

        let cookie_str = parts
            .headers
            .get(header::COOKIE)
            .and_then(|v| v.to_str().ok())
            .unwrap_or("");

        if let Some(token) = session::extract_cookie(cookie_str, COOKIE_NAME) {
            if let Some(entry) = state.sessions.get(&token) {
                if matches!(entry.role, Role::Admin) {
                    return Ok(AdminGuard);
                }
            }
        }

        // Prefer daemon-level login when a daemon admin token is configured.
        if state.daemon_admin_hash.is_some() {
            return Err(Redirect::to("/ui/manage/login").into_response());
        }

        // Fall back to first auth-enabled project login (stable: sorted by slug).
        let mut sorted = auth_projects;
        sorted.sort();
        if let Some(slug) = sorted.first() {
            return Err(Redirect::to(&format!("/ui/{slug}/login")).into_response());
        }
        Err(StatusCode::FORBIDDEN.into_response())
    }
}
