use leptos::prelude::*;
use serde::{Deserialize, Serialize};
use crate::codec::MsgPack;

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct UserResponse {
    pub id: i64,
    pub username: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Claims {
    pub sub: String, // username
    pub uid: i64,    // user id
    pub exp: usize,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SetupStatus {
    pub completed: bool,
}

#[server(GetSetupStatus, "/api/server_fns", input = MsgPack, output = MsgPack)]
pub async fn get_setup_status() -> Result<SetupStatus, ServerFnError> {
    use crate::DbContext;

    let db_context = use_context::<DbContext>().ok_or_else(|| ServerFnError::new("DB Context missing"))?;
    let has_users = db_context.db.has_users().await
        .map_err(|e| ServerFnError::new(format!("DB error: {}", e)))?;

    Ok(SetupStatus {
        completed: has_users,
    })
}

#[server(Setup, "/api/server_fns", input = MsgPack, output = MsgPack)]
pub async fn setup(username: String, password: String) -> Result<(), ServerFnError> {
    use crate::DbContext;

    let db_context = use_context::<DbContext>().ok_or_else(|| ServerFnError::new("DB Context missing"))?;
    
    // Check if setup is already done
    let has_users = db_context.db.has_users().await.unwrap_or(false);
    if has_users {
        return Err(ServerFnError::new("Setup already completed"));
    }

    // Hash password (low cost for MIPS)
    let password_hash = bcrypt::hash(&password, 6)
        .map_err(|_| ServerFnError::new("Hashing error"))?;

    db_context.db.create_user(&username, &password_hash).await
        .map_err(|e| ServerFnError::new(format!("DB error: {}", e)))?;

    Ok(())
}

#[server(Login, "/api/server_fns", input = MsgPack, output = MsgPack)]
pub async fn login(username: String, password: String) -> Result<UserResponse, ServerFnError> {
    use crate::DbContext;
    use leptos_axum::ResponseOptions;
    use jsonwebtoken::{encode, Header, EncodingKey};
    use cookie::{Cookie, SameSite};
    use std::time::{SystemTime, UNIX_EPOCH};

    let db_context = use_context::<DbContext>().ok_or_else(|| ServerFnError::new("DB Context missing"))?;
    
    let user_opt = db_context.db.get_user_by_username(&username).await
        .map_err(|e| ServerFnError::new(format!("DB error: {}", e)))?;

    if let Some((uid, password_hash)) = user_opt {
        let valid = bcrypt::verify(&password, &password_hash).unwrap_or(false);
        if !valid {
             return Err(ServerFnError::new("Invalid credentials"));
        }

        let expiration = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs() as usize + 24 * 3600; // 24 hours

        let claims = Claims {
            sub: username.clone(),
            uid,
            exp: expiration,
        };

        let secret = std::env::var("JWT_SECRET").unwrap_or_else(|_| "secret".to_string());
        let token = encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_bytes()))
            .map_err(|e| ServerFnError::new(format!("Token error: {}", e)))?;

        let cookie = Cookie::build(("auth_token", token))
            .path("/")
            .http_only(true)
            .same_site(SameSite::Strict)
            .build();

        if let Some(options) = use_context::<ResponseOptions>() {
            options.insert_header(
                axum::http::header::SET_COOKIE,
                axum::http::HeaderValue::from_str(&cookie.to_string()).unwrap(),
            );
        }

        Ok(UserResponse {
            id: uid,
            username,
        })
    } else {
        Err(ServerFnError::new("Invalid credentials"))
    }
}

#[server(Logout, "/api/server_fns", input = MsgPack, output = MsgPack)]
pub async fn logout() -> Result<(), ServerFnError> {
    use leptos_axum::ResponseOptions;
    use cookie::{Cookie, SameSite};

    let cookie = Cookie::build(("auth_token", ""))
        .path("/")
        .http_only(true)
        .same_site(SameSite::Strict)
        .max_age(cookie::time::Duration::seconds(0))
        .build();

    if let Some(options) = use_context::<ResponseOptions>() {
        options.insert_header(
            axum::http::header::SET_COOKIE,
            axum::http::HeaderValue::from_str(&cookie.to_string()).unwrap(),
        );
    }
    Ok(())
}

#[server(GetUser, "/api/server_fns", input = MsgPack, output = MsgPack)]
pub async fn get_user() -> Result<Option<UserResponse>, ServerFnError> {
    use axum::http::HeaderMap;
    use leptos_axum::extract;
    use jsonwebtoken::{decode, Validation, DecodingKey};

    let headers: HeaderMap = extract().await.map_err(|e| ServerFnError::new(format!("Extract error: {}", e)))?;
    let cookie_header = headers.get(axum::http::header::COOKIE)
        .and_then(|h| h.to_str().ok());

    if let Some(cookie_str) = cookie_header {
        for c_str in cookie_str.split(';') {
            if let Ok(c) = cookie::Cookie::parse(c_str.trim()) {
                if c.name() == "auth_token" {
                    let token = c.value();
                    let secret = std::env::var("JWT_SECRET").unwrap_or_else(|_| "secret".to_string());
                    let token_data = decode::<Claims>(
                        token,
                        &DecodingKey::from_secret(secret.as_bytes()),
                        &Validation::default(),
                    );

                    if let Ok(data) = token_data {
                        return Ok(Some(UserResponse {
                            id: data.claims.uid,
                            username: data.claims.sub,
                        }));
                    }
                }
            }
        }
    }

    Ok(None)
}