use std::sync::Arc;

use axum::{
    response::{Html, IntoResponse},
    routing::{get, post},
    Extension, Router,
};
use indieweb::standards::indieauth::{self, ClientId};
use miette::IntoDiagnostic;
use minijinja::Environment;
use rand::RngCore;
use tower_livereload::LiveReloadLayer;
use url::Url;

use crate::state::AppState;

fn engine() -> minijinja::Environment<'static> {
    let mut env = Environment::new();
    minijinja_embed::load_templates!(&mut env);
    env
}

mod client;
mod server;
mod state;

/// Landing page for the server
#[tracing::instrument]
async fn landing() -> impl IntoResponse {
    let engine = engine();
    let template = engine.get_template("index.html").unwrap();
    
    // Update the landing page to show server information
    let context = minijinja::context! {
        title => "IndieAuth Server Demo",
        server_info => "This is a complete IndieAuth server implementation with demo users",
        demo_users => vec![
            ("alice", "alice", "Basic profile (name only)"),
            ("bob", "bob", "Full profile with avatar"),
            ("charlie", "charlie", "External URL profile"),
            ("demo", "demo", "Simple profile with bot avatar"),
        ]
    };
    
    Html(template.render(context).unwrap())
}

/// Shared state for the client functionality
#[derive(Clone)]
pub struct ClientState {
    client_id: ClientId,
    client: Arc<indieauth::Client<indieweb::http::reqwest::Client>>,
}

impl ClientState {
    fn new(issuer: &Url) -> miette::Result<Self> {
        let client_id = ClientId::new(issuer.as_str())?;
        let client = Arc::new(indieauth::Client::new(
            client_id.as_str(),
            indieweb::http::reqwest::Client::default(),
        )?);
        Ok(Self { client_id, client })
    }
}

/// Generate a secure random signing key for JWT tokens
fn generate_signing_key() -> [u8; 32] {
    let mut key = [0u8; 32];
    rand::thread_rng().fill_bytes(&mut key);
    key
}

/// Auto-detect the server issuer URL from the bound address
fn determine_issuer(addr: &std::net::SocketAddr) -> Url {
    // For demo purposes, we'll use http and the actual bound address
    // In production, you'd want to use https and a proper domain
    format!("http://{}", addr)
        .parse()
        .expect("Failed to parse issuer URL")
}

#[tokio::main]
#[tracing::instrument]
async fn main() -> miette::Result<()> {
    tracing_forest::init();
    let livereload_layer = LiveReloadLayer::new();

    // Bind to address and determine issuer
    let listener = tokio::net::TcpListener::bind("0.0.0.0:8080")
        .await
        .into_diagnostic()?;
    
    let local_addr = listener.local_addr().into_diagnostic()?;
    let issuer = determine_issuer(&local_addr);
    
    tracing::info!("Server bound to: {}", local_addr);
    tracing::info!("Issuer URL: {}", issuer);

    // Generate signing key
    let signing_key = generate_signing_key();
    
    // Create server state
    let server_state = Arc::new(AppState::new(signing_key, issuer.clone()));
    
    // Create client state
    let client_state = ClientState::new(&issuer)?;

    // Build the application router
    let app = Router::new()
        // Landing page
        .route("/", get(landing))

        // Server endpoints (IndieAuth server functionality)
        .route("/.well-known/oauth-authorization-server", get(server::metadata::metadata_handler))
        .route("/auth", get(server::auth::auth_get).post(server::auth::auth_post))
        .route("/auth/confirm", post(server::auth::auth_confirm))
        .route("/token", post(server::token::token_handler))
        .route("/userinfo", get(server::userinfo::userinfo_handler))
        .route("/introspect", post(server::userinfo::introspection_handler))

        // Client endpoints (existing functionality)
        .nest("/client", client::router())

        // Layer configuration
        .layer(Extension(client_state))
        .layer(livereload_layer)
        .with_state(server_state);

    tracing::info!("Starting IndieAuth server at http://{}", local_addr);
    tracing::info!("Demo users available: alice/alice, bob/bob, charlie/charlie, demo/demo");
    tracing::info!("Test the server by visiting: http://{}/client", local_addr);
    
    axum::serve(listener, app).await.into_diagnostic()?;

    Ok(())
}