//! Dashboard login/logout handlers.
//!
//! Login accepts either user credentials (username + password) or a raw
//! API key. Successful logins set a session cookie with the configured
//! `Secure` flag policy. Logout drops both legacy cookie names so users
//! coming off an older session are fully cleaned up.

use askama::Template;
use axum::{
  Form,
  extract::State,
  http::StatusCode,
  response::{Html, IntoResponse, Redirect, Response},
};
use sha2::{Digest, Sha256};
use uuid::Uuid;

use super::templates::{LoginTemplate, UiTemplateConfig};
use crate::{
  session_cookie::{
    API_KEY_SESSION_COOKIE,
    USER_SESSION_COOKIE,
    api_key_session_cookie,
    clear_cookie,
    cookie_value,
    user_session_cookie,
  },
  state::AppState,
};

fn ui_config(state: &AppState) -> UiTemplateConfig {
  UiTemplateConfig::from_config(&state.config.ui)
}

pub(super) async fn login_page(State(state): State<AppState>) -> Html<String> {
  let tmpl = LoginTemplate {
    ui:        ui_config(&state),
    error:     None,
    is_admin:  false,
    auth_name: String::new(),
  };
  Html(
    tmpl
      .render()
      .unwrap_or_else(|e| format!("Template error: {e}")),
  )
}

#[derive(serde::Deserialize)]
pub(super) struct LoginForm {
  username: Option<String>,
  api_key:  Option<String>,
  password: Option<String>,
}

pub(super) async fn login_action(
  State(state): State<AppState>,
  Form(form): Form<LoginForm>,
) -> Response {
  // Try username/password authentication first
  if let (Some(username), Some(password)) =
    (form.username.as_ref(), form.password.as_ref())
  {
    let creds = circus_common::models::LoginCredentials {
      username: username.clone(),
      password: password.clone(),
    };

    if let Ok(user) =
      circus_common::repo::users::authenticate(&state.pool, &creds).await
    {
      crate::audit::record_with_actor(
        &state.pool,
        &circus_common::audit::Actor::user(user.id, user.username.clone()),
        None,
        "LOGIN_SUCCESS",
        Some("dashboard"),
        Some(&user.id.to_string()),
        serde_json::json!({ "method": "password" }),
      )
      .await;

      let session = match circus_common::repo::users::create_session(
        &state.pool,
        user.id,
      )
      .await
      {
        Ok(session) => session,
        Err(e) => {
          tracing::error!(user_id = %user.id, "failed to create user session: {e}");
          return StatusCode::INTERNAL_SERVER_ERROR.into_response();
        },
      };

      let cookie = user_session_cookie(&session.0, &state.config.server);
      return (
        [(axum::http::header::SET_COOKIE, cookie)],
        Redirect::to("/"),
      )
        .into_response();
    }
    crate::audit::record_with_actor(
      &state.pool,
      &circus_common::audit::Actor::anonymous(),
      None,
      "LOGIN_FAILURE",
      Some("dashboard"),
      Some(username),
      serde_json::json!({ "method": "password" }),
    )
    .await;

    let tmpl = LoginTemplate {
      ui:        ui_config(&state),
      error:     Some("Invalid username or password".to_string()),
      is_admin:  false,
      auth_name: String::new(),
    };
    return (
      StatusCode::UNAUTHORIZED,
      Html(
        tmpl
          .render()
          .unwrap_or_else(|e| format!("Template error: {e}")),
      ),
    )
      .into_response();
  }

  // Fall back to API key authentication
  if let Some(token) = form.api_key.as_ref() {
    let token = token.trim();
    if token.is_empty() {
      let tmpl = LoginTemplate {
        ui:        ui_config(&state),
        error:     Some("API key is required".to_string()),
        is_admin:  false,
        auth_name: String::new(),
      };
      return Html(
        tmpl
          .render()
          .unwrap_or_else(|e| format!("Template error: {e}")),
      )
      .into_response();
    }

    let mut hasher = Sha256::new();
    hasher.update(token.as_bytes());
    let key_hash = hex::encode(hasher.finalize());

    if let Ok(Some(api_key)) =
      circus_common::repo::api_keys::get_by_hash(&state.pool, &key_hash).await
    {
      crate::audit::record_with_actor(
        &state.pool,
        &circus_common::audit::Actor::api_key(api_key.id, api_key.name.clone()),
        None,
        "LOGIN_SUCCESS",
        Some("dashboard"),
        Some(&api_key.id.to_string()),
        serde_json::json!({ "method": "api_key" }),
      )
      .await;

      let session_id = Uuid::new_v4().to_string();
      state
        .sessions
        .insert(session_id.clone(), crate::state::SessionData {
          api_key:    Some(api_key),
          user:       None,
          created_at: std::time::Instant::now(),
        });

      let cookie = api_key_session_cookie(&session_id, &state.config.server);
      (
        [(axum::http::header::SET_COOKIE, cookie)],
        Redirect::to("/"),
      )
        .into_response()
    } else {
      crate::audit::record_with_actor(
        &state.pool,
        &circus_common::audit::Actor::anonymous(),
        None,
        "LOGIN_FAILURE",
        Some("dashboard"),
        None,
        serde_json::json!({ "method": "api_key" }),
      )
      .await;

      let tmpl = LoginTemplate {
        ui:        ui_config(&state),
        error:     Some("Invalid API key".to_string()),
        is_admin:  false,
        auth_name: String::new(),
      };
      Html(
        tmpl
          .render()
          .unwrap_or_else(|e| format!("Template error: {e}")),
      )
      .into_response()
    }
  } else {
    let tmpl = LoginTemplate {
      ui:        ui_config(&state),
      error:     Some(
        "Please provide either username/password or API key".to_string(),
      ),
      is_admin:  false,
      auth_name: String::new(),
    };
    Html(
      tmpl
        .render()
        .unwrap_or_else(|e| format!("Template error: {e}")),
    )
    .into_response()
  }
}

pub(super) async fn logout_action(
  State(state): State<AppState>,
  request: axum::extract::Request,
) -> Response {
  // Remove server-side session for both cookie types
  {
    // Check for user session
    if let Some(session_id) =
      cookie_value(request.headers(), USER_SESSION_COOKIE)
      && let Err(e) =
        circus_common::repo::users::delete_session(&state.pool, &session_id)
          .await
    {
      tracing::warn!("failed to delete user session during logout: {e}");
    }

    // Check for legacy API key session
    if let Some(session_id) =
      cookie_value(request.headers(), API_KEY_SESSION_COOKIE)
    {
      state.sessions.remove(&session_id);
    }
  }

  // Clear both cookies
  let cookies = [
    clear_cookie(USER_SESSION_COOKIE, &state.config.server),
    clear_cookie(API_KEY_SESSION_COOKIE, &state.config.server),
  ];
  (
    [
      (axum::http::header::SET_COOKIE, cookies[0].clone()),
      (axum::http::header::SET_COOKIE, cookies[1].clone()),
    ],
    Redirect::to("/"),
  )
    .into_response()
}
