use postgres::NoTls;
use r2d2::PooledConnection;
use r2d2_postgres::PostgresConnectionManager;
use rouille::{Request, Response};
use serde::Serialize;
use serde_json::json;
use std::net::{IpAddr, Ipv4Addr};
use std::sync::Arc;

use crate::routes::AppState;

type PgConn = PooledConnection<PostgresConnectionManager<NoTls>>;

/// Standard JSON error response with HTTP status code.
pub fn json_error(status: u16, error: &str) -> Response {
    Response::json(&json!({"error": error})).with_status_code(status)
}

/// Get a database connection from the pool, returning a 500 error response on failure.
pub fn get_conn(state: &Arc<AppState>) -> Result<PgConn, Response> {
    state
        .pool
        .get()
        .map_err(|_| json_error(500, "Database connection error"))
}

/// Authenticated user information extracted from a valid session.
#[derive(Debug, Clone, Serialize)]
pub struct AuthUser {
    pub id: i32,
    pub username: String,
    pub email: String,
    pub role: String,
}

/// Validate the session cookie and return the authenticated user.
/// Returns 401 if no cookie, expired session, or invalid session.
pub fn require_auth(request: &Request, conn: &mut PgConn) -> Result<AuthUser, Response> {
    let session_cookie = get_session_cookie(request)
        .ok_or_else(|| json_error(401, "Authentication required"))?;

    let row = conn
        .query_opt(
            "SELECT u.id, u.username, u.email, u.role \
             FROM sessions s JOIN users u ON u.id = s.user_id \
             WHERE s.id = $1 AND s.expires_at > NOW()",
            &[&session_cookie],
        )
        .map_err(|_| json_error(500, "Database error"))?
        .ok_or_else(|| json_error(401, "Session not found or expired"))?;

    Ok(AuthUser {
        id: row.get(0),
        username: row.get(1),
        email: row.get(2),
        role: row.get(3),
    })
}

/// Extract the session_id cookie value from the request.
pub fn get_session_cookie(request: &Request) -> Option<String> {
    request.header("Cookie").and_then(|cookies| {
        cookies
            .split(';')
            .find(|c| c.trim().starts_with("session_id="))
            .map(|c| c.trim().trim_start_matches("session_id=").to_string())
    })
}

/// Extract the client IP address from proxy headers or fallback to localhost.
pub fn client_ip(request: &Request) -> IpAddr {
    let ip_str = request
        .header("X-Real-IP")
        .or_else(|| request.header("X-Forwarded-For"))
        .unwrap_or("127.0.0.1");
    ip_str
        .parse()
        .unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST))
}

/// Extract User-Agent header with a fallback.
pub fn user_agent(request: &Request) -> String {
    request
        .header("User-Agent")
        .unwrap_or("unknown")
        .to_string()
}
