use std::sync::{Arc, Mutex};

use axum::{Extension, Json};
use axum_extra::{
    TypedHeader,
    extract::{
        CookieJar,
        cookie::{Cookie, SameSite},
    },
    headers::UserAgent,
};
use defguard_certs::{CertificateInfo, der_to_pem, parse_pem_certificate};
use defguard_common::{
    db::models::{
        Certificates, Session, SessionState, Settings, User,
        group::Group,
        initial_setup_wizard::{InitialSetupState, InitialSetupStep},
        proxy::Proxy,
        settings::update_current_settings,
        setup_auto_adoption::{AutoAdoptionWizardState, AutoAdoptionWizardStep},
        wizard::{ActiveWizard, Wizard},
    },
    types::proxy::ProxyInfo,
};
use defguard_core::{
    auth::{
        AdminOrSetupRole, SessionInfo,
        failed_login::{FailedLoginMap, check_failed_logins, log_failed_login_attempt},
    },
    error::WebError,
    handlers::{ApiResponse, ApiResult, ClientIpAddr, SESSION_COOKIE_NAME},
    headers::get_device_info,
};
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use serde_json::json;
use sqlx::PgPool;
use tokio::sync::oneshot;
use tracing::{debug, info};

use crate::handlers::auto_wizard::{advance_auto_wizard_to_step, is_auto_wizard_active};

pub(crate) async fn advance_initial_wizard_to_step(
    pool: &PgPool,
    step: InitialSetupStep,
) -> Result<(), WebError> {
    let wizard = Wizard::get(pool).await?;

    // Don't try to advance if the initial wizard is not active
    if wizard.active_wizard != ActiveWizard::Initial {
        debug!("Not advancing initial wizard step as initial wizard is not active");
        return Ok(());
    }

    // Don't try to advance if setup is already completed
    if wizard.completed {
        debug!("Not advancing setup step as initial setup is already completed");
        return Ok(());
    }

    let current_step = InitialSetupState::get(pool)
        .await?
        .map_or(InitialSetupStep::Welcome, |s| s.step);
    if current_step < step {
        InitialSetupState { step }.save(pool).await?;
        info!("Advanced initial wizard setup to step {:?}", step);
    } else {
        debug!(
            "Not advancing initial wizard setup step from {current_step:?} to {step:?} as it is not a forward step"
        );
    }
    Ok(())
}

async fn advance_wizard_to_step(
    pool: &PgPool,
    initial_step: Option<InitialSetupStep>,
    auto_step: Option<AutoAdoptionWizardStep>,
) -> Result<(), WebError> {
    if let Some(step) = auto_step
        && is_auto_wizard_active(pool).await?
    {
        advance_auto_wizard_to_step(pool, step).await?;
    }

    if let Some(initial_step) = initial_step
        && !is_auto_wizard_active(pool).await?
    {
        advance_initial_wizard_to_step(pool, initial_step).await?;
    }

    Ok(())
}

#[derive(Deserialize, Serialize, Debug)]
pub struct CreateAdmin {
    first_name: String,
    last_name: String,
    username: String,
    email: String,
    password: String,
    #[serde(default)]
    automatically_assign_group: bool,
}

#[derive(Deserialize, Serialize, Debug)]
pub struct SetupLogin {
    username: String,
    password: String,
}

pub async fn create_admin(
    cookies: CookieJar,
    user_agent: TypedHeader<UserAgent>,
    ClientIpAddr(ip_addr): ClientIpAddr,
    Extension(pool): Extension<PgPool>,
    Json(admin): Json<CreateAdmin>,
) -> Result<(CookieJar, ApiResponse), WebError> {
    advance_wizard_to_step(
        &pool,
        Some(InitialSetupStep::AdminUser),
        Some(AutoAdoptionWizardStep::AdminUser),
    )
    .await?;
    info!(
        "Creating initial admin user {} ({})",
        admin.username, admin.email
    );
    let user = User::new(
        admin.username,
        Some(admin.password.as_str()),
        admin.last_name,
        admin.first_name,
        admin.email,
        None,
    )
    .save(&pool)
    .await?;

    debug!("Initial admin user created with ID {}", user.id);
    let mut settings = Settings::get_current_settings();
    settings.default_admin_id = Some(user.id);
    update_current_settings(&pool, settings).await?;
    debug!("Initial admin user set as default admin in settings");

    if admin.automatically_assign_group {
        let settings = Settings::get_current_settings();
        let default_admin_group_name = settings.default_admin_group_name;

        let admin_group =
            if let Some(mut group) = Group::find_by_name(&pool, &default_admin_group_name).await? {
                group.is_admin = true;
                group.save(&pool).await?;
                group
            } else {
                let mut group = Group::new(&default_admin_group_name);
                group.is_admin = true;
                group.save(&pool).await?
            };

        user.add_to_group(&pool, &admin_group).await?;

        debug!(
            "Automatically assigned admin user {} to admin group {}",
            user.username, admin_group.name
        );
    }

    let device_info = get_device_info(user_agent.as_str());

    Session::delete_expired(&pool).await?;
    let session = Session::new(
        user.id,
        SessionState::PasswordVerified,
        ip_addr.to_string(),
        Some(device_info),
    );
    session.save(&pool).await?;

    let auth_cookie = Cookie::build((SESSION_COOKIE_NAME, session.id.clone()))
        .path("/")
        .http_only(true)
        .same_site(SameSite::Lax);
    let cookies = cookies.add(auth_cookie);

    info!("Initial admin user created");

    advance_wizard_to_step(
        &pool,
        Some(InitialSetupStep::GeneralConfiguration),
        Some(AutoAdoptionWizardStep::UrlSettings),
    )
    .await?;

    Ok((cookies, ApiResponse::with_status(StatusCode::CREATED)))
}

pub async fn setup_login(
    cookies: CookieJar,
    user_agent: TypedHeader<UserAgent>,
    ClientIpAddr(ip_addr): ClientIpAddr,
    Extension(pool): Extension<PgPool>,
    Extension(failed_logins): Extension<Arc<Mutex<FailedLoginMap>>>,
    Json(login): Json<SetupLogin>,
) -> Result<(CookieJar, ApiResponse), WebError> {
    let wizard = Wizard::get(&pool).await?;
    if wizard.completed {
        return Err(WebError::Forbidden("Initial setup already completed"));
    }
    let settings = Settings::get_current_settings();
    let default_admin_id = settings
        .default_admin_id
        .ok_or_else(|| WebError::Forbidden("Default admin user not set"))?;

    check_failed_logins(&failed_logins, &login.username)?;

    let mut conn = pool.acquire().await?;
    let Some(user) = User::find_by_username_or_email(&mut conn, &login.username).await? else {
        log_failed_login_attempt(&failed_logins, &login.username);
        return Err(WebError::Authentication);
    };

    if user.verify_password(&login.password).is_err() {
        log_failed_login_attempt(&failed_logins, &login.username);
        return Err(WebError::Authentication);
    }

    if !user.is_active {
        return Err(WebError::Authentication);
    }

    if user.id != default_admin_id {
        return Err(WebError::Forbidden("access denied"));
    }

    let device_info = get_device_info(user_agent.as_str());

    Session::delete_expired(&pool).await?;
    let session = Session::new(
        user.id,
        SessionState::PasswordVerified,
        ip_addr.to_string(),
        Some(device_info),
    );
    session.save(&pool).await?;

    let auth_cookie = Cookie::build((SESSION_COOKIE_NAME, session.id.clone()))
        .path("/")
        .http_only(true)
        .same_site(SameSite::Lax);
    let cookies = cookies.add(auth_cookie);

    Ok((cookies, ApiResponse::with_status(StatusCode::OK)))
}

pub async fn setup_session(session: SessionInfo, Extension(pool): Extension<PgPool>) -> ApiResult {
    let wizard = Wizard::get(&pool).await?;
    if wizard.completed {
        return Err(WebError::Forbidden("Initial setup already completed"));
    }
    let settings = Settings::get_current_settings();
    let default_admin_id = settings
        .default_admin_id
        .ok_or_else(|| WebError::Forbidden("Default admin user not set"))?;
    if session.user.id != default_admin_id {
        return Err(WebError::Forbidden("access denied"));
    }
    Ok(ApiResponse::with_status(StatusCode::OK))
}

#[derive(Deserialize, Serialize, Debug)]
pub struct GeneralConfig {
    default_admin_group_name: String,
    default_authentication: u32,
    default_mfa_code_lifetime: u32,
}

pub async fn set_general_config(
    _: AdminOrSetupRole,
    Extension(pool): Extension<PgPool>,
    Json(general_config): Json<GeneralConfig>,
) -> ApiResult {
    info!("Applying initial general configuration settings");
    debug!(
        "General configuration received: default_admin_group_name={}, default_authentication={}, default_mfa_code_lifetime={}",
        general_config.default_admin_group_name,
        general_config.default_authentication,
        general_config.default_mfa_code_lifetime,
    );
    let default_admin_group_name = general_config.default_admin_group_name.clone();
    let mut settings = Settings::get_current_settings();
    settings.default_admin_group_name = general_config.default_admin_group_name;
    settings.authentication_period_days = general_config
        .default_authentication
        .try_into()
        .map_err(|err| {
            WebError::BadRequest(format!("Invalid authentication period days: {err}"))
        })?;
    settings.mfa_code_timeout_seconds = general_config
        .default_mfa_code_lifetime
        .try_into()
        .map_err(|err| WebError::BadRequest(format!("Invalid MFA code timeout seconds: {err}")))?;
    update_current_settings(&pool, settings).await?;
    let settings = Settings::get_current_settings();
    debug!("Settings persisted");

    let admin_group =
        if let Some(mut group) = Group::find_by_name(&pool, &default_admin_group_name).await? {
            debug!(
                "Admin group {} found, marking as admin",
                default_admin_group_name
            );
            group.is_admin = true;
            group.save(&pool).await?;
            group
        } else {
            debug!(
                "Admin group {} not found, creating",
                default_admin_group_name
            );
            let mut group = Group::new(&default_admin_group_name);
            group.is_admin = true;
            group.save(&pool).await?
        };

    let admin_id = settings
        .default_admin_id
        .ok_or_else(|| WebError::DbError("Default admin user ID not set in settings".into()))?;

    let admin_user = User::find_by_id(&pool, admin_id).await?.ok_or_else(|| {
        WebError::ObjectNotFound(format!("Admin user with ID '{admin_id}' not found"))
    })?;
    debug!(
        "Assigning admin user {} to admin group {}",
        admin_user.username, admin_group.name
    );
    admin_user.add_to_group(&pool, &admin_group).await?;

    info!("Initial general configuration applied");

    advance_initial_wizard_to_step(&pool, InitialSetupStep::Ca).await?;

    Ok(ApiResponse::with_status(StatusCode::CREATED))
}

#[derive(Deserialize, Serialize, Debug)]
pub struct CreateCA {
    common_name: String,
    email: String,
    validity_period_years: u32,
}

pub async fn create_ca(
    _: AdminOrSetupRole,
    Extension(pool): Extension<PgPool>,
    Json(ca_info): Json<CreateCA>,
) -> ApiResult {
    info!("Creating new certificate authority");
    debug!(
        "CA request details: common_name={}, email={}, validity_period_years={}",
        ca_info.common_name, ca_info.email, ca_info.validity_period_years
    );
    let ca = defguard_certs::CertificateAuthority::new(
        &ca_info.common_name,
        &ca_info.email,
        ca_info.validity_period_years * 365,
    )?;

    let (cert_der, key_der) = (ca.cert_der().to_vec(), ca.key_pair_der().to_vec());

    let mut certs = Certificates::get_or_default(&pool)
        .await
        .map_err(WebError::from)?;
    certs.ca_cert_der = Some(cert_der);
    certs.ca_key_der = Some(key_der);
    certs.ca_expiry = Some(ca.expiry()?);
    certs.save(&pool).await.map_err(WebError::from)?;

    info!("Certificate authority created and stored");

    let wizard = Wizard::get(&pool).await?;

    if wizard.active_wizard == ActiveWizard::Initial {
        InitialSetupState::set_step(&pool, InitialSetupStep::CaSummary).await?;
    }

    Ok(ApiResponse::with_status(StatusCode::CREATED))
}

pub async fn get_ca(_: AdminOrSetupRole, Extension(pool): Extension<PgPool>) -> ApiResult {
    debug!("Fetching certificate authority details");
    let certs = Certificates::get_or_default(&pool)
        .await
        .map_err(WebError::from)?;
    let wizard = Wizard::get(&pool).await?;
    if let Some(ca_cert_der) = certs.ca_cert_der {
        let ca_pem = der_to_pem(&ca_cert_der, defguard_certs::PemLabel::Certificate)?;
        let info = CertificateInfo::from_der(&ca_cert_der)?;
        let valid_for_days = (info.not_after.and_utc() - chrono::Utc::now()).num_days();

        debug!(
            "Certificate authority details prepared: subject_common_name={}, valid_for_days={}",
            info.subject_common_name, valid_for_days
        );

        if wizard.active_wizard == ActiveWizard::Initial {
            InitialSetupState::set_step(&pool, InitialSetupStep::EdgeComponent).await?;
        }

        Ok(ApiResponse::new(
            json!({
                "ca_cert_pem": ca_pem,
                "subject_common_name": info.subject_common_name,
                "not_before": info.not_before,
                "not_after": info.not_after,
                "valid_for_days": valid_for_days
            }),
            StatusCode::OK,
        ))
    } else {
        Err(WebError::ObjectNotFound(
            "CA certificate not found".to_string(),
        ))
    }
}

#[derive(Deserialize, Serialize, Debug)]
pub struct UploadCA {
    cert_file: String,
}

pub async fn upload_ca(
    _: AdminOrSetupRole,
    Extension(pool): Extension<PgPool>,
    Json(ca_info): Json<UploadCA>,
) -> ApiResult {
    info!("Uploading existing certificate authority");
    let cert_der = parse_pem_certificate(&ca_info.cert_file)?;
    let expiry = CertificateInfo::from_der(&cert_der)?.not_after;

    let mut certs = Certificates::get_or_default(&pool)
        .await
        .map_err(WebError::from)?;
    certs.ca_cert_der = Some(cert_der.to_vec());
    certs.ca_key_der = None; // Key is not provided when uploading CA
    certs.ca_expiry = Some(expiry);
    certs.save(&pool).await.map_err(WebError::from)?;

    advance_initial_wizard_to_step(&pool, InitialSetupStep::CaSummary).await?;

    info!("Certificate authority uploaded and stored");

    Ok(ApiResponse::with_status(StatusCode::CREATED))
}

pub async fn finish_setup(
    _: AdminOrSetupRole,
    Extension(pool): Extension<PgPool>,
    Extension(setup_shutdown_tx): Extension<Arc<Mutex<Option<oneshot::Sender<()>>>>>,
) -> ApiResult {
    info!("Finishing initial setup");

    let mut wizard = Wizard::get(&pool).await?;
    InitialSetupState {
        step: InitialSetupStep::Finished,
    }
    .save(&pool)
    .await?;
    wizard.completed = true;
    wizard.active_wizard = ActiveWizard::None;
    wizard.save(&pool).await?;
    if let Some(tx) = setup_shutdown_tx
        .lock()
        .expect("Failed to lock setup shutdown sender")
        .take()
    {
        let _ = tx.send(());
        info!("Initial setup completed and shutdown signal sent");
    } else {
        return Err(WebError::BadRequest(
            "Setup shutdown sender no longer available".to_string(),
        ));
    }

    Ok(ApiResponse::with_status(StatusCode::OK))
}

#[derive(Serialize)]
struct WizardStateResponse {
    active_wizard: ActiveWizard,
    completed: bool,
    initial_setup_state: Option<InitialSetupState>,
    auto_adoption_state: Option<AutoAdoptionWizardState>,
}

/// Returns the full wizard state (active wizard, step states, etc.).
/// Used by the frontend to determine which wizard
/// to show and what step to resume.
pub async fn get_wizard_state(Extension(pool): Extension<PgPool>) -> ApiResult {
    let wizard = Wizard::get(&pool).await?;

    let initial_setup_state = if wizard.active_wizard == ActiveWizard::Initial {
        InitialSetupState::get(&pool).await?
    } else {
        None
    };
    let auto_adoption_state = if wizard.active_wizard == ActiveWizard::AutoAdoption {
        AutoAdoptionWizardState::get(&pool).await?
    } else {
        None
    };

    Ok(ApiResponse::json(
        WizardStateResponse {
            active_wizard: wizard.active_wizard,
            completed: wizard.completed,
            initial_setup_state,
            auto_adoption_state,
        },
        StatusCode::OK,
    ))
}

pub async fn proxy_list(_: AdminOrSetupRole, Extension(pool): Extension<PgPool>) -> ApiResult {
    let proxies = Proxy::list(&pool).await?;
    let proxies: Vec<ProxyInfo> = proxies.into_iter().map(Into::into).collect();

    Ok(ApiResponse::json(proxies, StatusCode::OK))
}
