pub mod session_verifier;

use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;

use crate::connectivity::ConnectivityMonitor;
use crate::jellyfin::http_client::HttpClient;

pub use session_verifier::SessionVerifier;

/// Server information returned from Jellyfin
#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[specta(rename = "AuthServerInfo")]
pub struct ServerInfo {
    pub name: String,
    pub version: String,
    pub id: String,
    /// Normalized server URL with protocol and no trailing slash
    pub normalized_url: String,
}

/// User information
#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct User {
    pub id: String,
    pub name: String,
    pub server_id: String,
    pub primary_image_tag: Option<String>,
}

/// Authentication result
#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthResult {
    pub user: User,
    pub access_token: String,
    pub server_id: String,
}

/// Active session for restoration
#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Session {
    pub user_id: String,
    pub username: String,
    pub server_id: String,
    pub server_url: String,
    pub server_name: String,
    pub access_token: String,
    pub verified: bool,
    pub needs_reauth: bool,
}

// Jellyfin API response types (PascalCase from server)

#[derive(specta::Type, Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
struct PublicSystemInfo {
    server_name: String,
    version: String,
    id: String,
}

#[derive(specta::Type, Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
struct AuthenticateByNameResponse {
    user: JellyfinUser,
    access_token: String,
    server_id: String,
}

#[derive(specta::Type, Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
struct JellyfinUser {
    id: String,
    name: String,
    server_id: String,
    primary_image_tag: Option<String>,
}

/// Authentication manager
pub struct AuthManager {
    http_client: Arc<HttpClient>,
    current_session: Arc<RwLock<Option<Session>>>,
    connectivity_monitor: Option<Arc<tokio::sync::Mutex<ConnectivityMonitor>>>,
}

impl AuthManager {
    /// Create a new auth manager
    pub fn new(http_client: HttpClient) -> Self {
        Self {
            http_client: Arc::new(http_client),
            current_session: Arc::new(RwLock::new(None)),
            connectivity_monitor: None,
        }
    }

    /// Set the connectivity monitor (for marking server reachability)
    pub fn set_connectivity_monitor(
        &mut self,
        monitor: Arc<tokio::sync::Mutex<ConnectivityMonitor>>,
    ) {
        self.connectivity_monitor = Some(monitor);
    }

    /// Normalize and validate server URL.
    /// Enforces HTTPS — plain HTTP is rejected for security.
    pub fn normalize_url(url: &str) -> Result<String, String> {
        let mut normalized = url.trim().to_string();

        // Reject plain HTTP — all connections must use HTTPS
        if normalized.starts_with("http://") {
            return Err("HTTP connections are not allowed. Please use HTTPS (e.g., https://your-server.com).".to_string());
        }

        // Add https:// if no protocol specified
        if !normalized.starts_with("https://") {
            normalized = format!("https://{}", normalized);
        }

        // Remove trailing slash
        if normalized.ends_with('/') {
            normalized.pop();
        }

        Ok(normalized)
    }

    /// Normalize a username before it goes to the server.
    ///
    /// Only surrounding whitespace is stripped — interior spaces are legal in
    /// Jellyfin usernames. Without this, a trailing space from a soft keyboard's
    /// autocorrect makes the server report an unknown user, which surfaces as a
    /// 401 that looks exactly like a wrong password.
    ///
    /// TRACES: UR-042 | DR-054
    pub fn normalize_username(username: &str) -> String {
        username.trim().to_string()
    }

    /// Connect to server and get server info
    pub async fn connect_to_server(&self, server_url: &str) -> Result<ServerInfo, String> {
        let normalized_url = Self::normalize_url(server_url)?;
        let endpoint = format!("{}/System/Info/Public", normalized_url);

        log::info!("[AuthManager] Connecting to server: {}", normalized_url);

        match self
            .http_client
            .get_json_fast::<PublicSystemInfo>(&endpoint)
            .await
        {
            Ok(info) => {
                log::info!(
                    "[AuthManager] Connected to server: {} ({})",
                    info.server_name,
                    info.version
                );

                // Mark server as reachable
                if let Some(monitor) = &self.connectivity_monitor {
                    let monitor = monitor.lock().await;
                    monitor.mark_reachable().await;
                }

                Ok(ServerInfo {
                    name: info.server_name,
                    version: info.version,
                    id: info.id,
                    normalized_url,
                })
            }
            Err(e) => {
                log::error!("[AuthManager] Failed to connect to server: {}", e);

                // Mark server as unreachable
                if let Some(monitor) = &self.connectivity_monitor {
                    let monitor = monitor.lock().await;
                    monitor.mark_unreachable(Some(e.clone())).await;
                }

                Err(e)
            }
        }
    }

    /// Authenticate by username and password
    pub async fn login(
        &self,
        server_url: &str,
        username: &str,
        password: &str,
        device_id: &str,
    ) -> Result<AuthResult, String> {
        let url = Self::normalize_url(server_url)?;
        let endpoint = format!("{}/Users/AuthenticateByName", url);
        let username = Self::normalize_username(username);

        log::info!("[AuthManager] Authenticating user: {}", username);

        // Build auth header for login request
        let auth_header = HttpClient::build_auth_header(None, device_id);

        // Build request manually for custom headers
        let request = self
            .http_client
            .client
            .post(&endpoint)
            .header("Content-Type", "application/json")
            .header("X-Emby-Authorization", auth_header)
            .json(&serde_json::json!({
                "Username": username,
                "Pw": password,
            }))
            .build()
            .map_err(|e| format!("Failed to build request: {}", e))?;

        // Use retry logic
        let response = self
            .http_client
            .request_with_retry(request)
            .await
            .map_err(|e| format!("Login request failed: {}", e))?;

        if !response.status().is_success() {
            let status = response.status();
            let error_text = response
                .text()
                .await
                .unwrap_or_else(|_| "Unknown error".to_string());
            return Err(format!("Login failed: HTTP {}: {}", status, error_text));
        }

        let auth_response: AuthenticateByNameResponse = response
            .json()
            .await
            .map_err(|e| format!("Failed to parse login response: {}", e))?;

        log::info!(
            "[AuthManager] Login successful for user: {} ({})",
            auth_response.user.name,
            auth_response.user.id
        );

        // Mark server as reachable
        if let Some(monitor) = &self.connectivity_monitor {
            let monitor = monitor.lock().await;
            monitor.mark_reachable().await;
        }

        let user = User {
            id: auth_response.user.id,
            name: auth_response.user.name,
            server_id: auth_response.user.server_id,
            primary_image_tag: auth_response.user.primary_image_tag,
        };

        Ok(AuthResult {
            user,
            access_token: auth_response.access_token,
            server_id: auth_response.server_id,
        })
    }

    /// Verify current session by fetching user info
    pub async fn verify_session(
        &self,
        server_url: &str,
        user_id: &str,
        access_token: &str,
        device_id: &str,
    ) -> Result<User, String> {
        let url = Self::normalize_url(server_url)?;
        let endpoint = format!("{}/Users/{}", url, user_id);

        log::info!("[AuthManager] Verifying session for user: {}", user_id);

        // Build auth header
        let auth_header = HttpClient::build_auth_header(Some(access_token), device_id);

        // Build request manually for custom headers
        let request = self
            .http_client
            .client
            .get(&endpoint)
            .header("X-Emby-Authorization", auth_header)
            .build()
            .map_err(|e| format!("Failed to build request: {}", e))?;

        // Use retry logic
        let response = self
            .http_client
            .request_with_retry(request)
            .await
            .map_err(|e| {
                log::warn!("[AuthManager] Session verification failed: {}", e);
                format!("Session verification failed: {}", e)
            })?;

        if !response.status().is_success() {
            let status = response.status();
            let error_text = response
                .text()
                .await
                .unwrap_or_else(|_| "Unknown error".to_string());

            // Mark server as unreachable for auth errors
            if status.as_u16() == 401 || status.as_u16() == 403 {
                log::warn!("[AuthManager] Session invalid: HTTP {}", status);
                if let Some(monitor) = &self.connectivity_monitor {
                    let monitor = monitor.lock().await;
                    monitor
                        .mark_unreachable(Some(format!("Authentication failed: {}", status)))
                        .await;
                }
            }

            return Err(format!("HTTP {}: {}", status, error_text));
        }

        let user_response: JellyfinUser = response
            .json()
            .await
            .map_err(|e| format!("Failed to parse user response: {}", e))?;

        log::info!(
            "[AuthManager] Session verified successfully for: {}",
            user_response.name
        );

        // Mark server as reachable
        if let Some(monitor) = &self.connectivity_monitor {
            let monitor = monitor.lock().await;
            monitor.mark_reachable().await;
        }

        Ok(User {
            id: user_response.id,
            name: user_response.name,
            server_id: user_response.server_id,
            primary_image_tag: user_response.primary_image_tag,
        })
    }

    /// Logout (call Jellyfin logout endpoint)
    pub async fn logout(
        &self,
        server_url: &str,
        access_token: &str,
        device_id: &str,
    ) -> Result<(), String> {
        let url = Self::normalize_url(server_url)?;
        let endpoint = format!("{}/Sessions/Logout", url);

        log::info!("[AuthManager] Logging out");

        // Build auth header
        let auth_header = HttpClient::build_auth_header(Some(access_token), device_id);

        // Build request
        let request = self
            .http_client
            .client
            .post(&endpoint)
            .header("X-Emby-Authorization", auth_header)
            .build()
            .map_err(|e| format!("Failed to build request: {}", e))?;

        // Don't retry logout - if it fails, we'll still clear local state
        match self.http_client.client.execute(request).await {
            Ok(response) => {
                if response.status().is_success() {
                    log::info!("[AuthManager] Logout successful");
                } else {
                    log::warn!("[AuthManager] Logout request failed: {}", response.status());
                }
            }
            Err(e) => {
                log::warn!("[AuthManager] Logout request failed: {}", e);
            }
        }

        Ok(())
    }

    /// Get current session
    pub async fn get_session(&self) -> Option<Session> {
        self.current_session.read().await.clone()
    }

    /// Set current session
    pub async fn set_session(&self, session: Option<Session>) {
        *self.current_session.write().await = session;
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// Test URL normalization - adds https:// when missing
    #[test]
    fn test_normalize_url_adds_https() {
        assert_eq!(
            AuthManager::normalize_url("jellyfin.example.com").unwrap(),
            "https://jellyfin.example.com"
        );
        assert_eq!(
            AuthManager::normalize_url("192.168.1.100:8096").unwrap(),
            "https://192.168.1.100:8096"
        );
    }

    /// Test URL normalization - preserves existing https
    #[test]
    fn test_normalize_url_preserves_https() {
        assert_eq!(
            AuthManager::normalize_url("https://jellyfin.example.com").unwrap(),
            "https://jellyfin.example.com"
        );
    }

    /// Test URL normalization - rejects HTTP
    #[test]
    fn test_normalize_url_rejects_http() {
        assert!(AuthManager::normalize_url("http://localhost:8096").is_err());
        assert!(AuthManager::normalize_url("http://jellyfin.example.com").is_err());
    }

    /// Test URL normalization - removes trailing slash
    #[test]
    fn test_normalize_url_removes_trailing_slash() {
        assert_eq!(
            AuthManager::normalize_url("https://jellyfin.example.com/").unwrap(),
            "https://jellyfin.example.com"
        );
        assert_eq!(
            AuthManager::normalize_url("jellyfin.example.com/").unwrap(),
            "https://jellyfin.example.com"
        );
    }

    /// Test URL normalization - trims whitespace
    #[test]
    fn test_normalize_url_trims_whitespace() {
        assert_eq!(
            AuthManager::normalize_url("  jellyfin.example.com  ").unwrap(),
            "https://jellyfin.example.com"
        );
        assert_eq!(
            AuthManager::normalize_url("  https://jellyfin.example.com/  ").unwrap(),
            "https://jellyfin.example.com"
        );
    }

    /// Usernames must be trimmed before they reach the server: the Android soft
    /// keyboard appends a trailing space after autocorrect, and Jellyfin then
    /// reports an unknown user — a 401 indistinguishable from a wrong password.
    #[test]
    fn test_normalize_username_trims_whitespace() {
        assert_eq!(AuthManager::normalize_username("duncan "), "duncan");
        assert_eq!(AuthManager::normalize_username(" duncan"), "duncan");
        assert_eq!(AuthManager::normalize_username("  duncan  "), "duncan");
        assert_eq!(AuthManager::normalize_username("duncan\n"), "duncan");
    }

    /// Interior spaces are legal in Jellyfin usernames and must survive.
    #[test]
    fn test_normalize_username_preserves_interior_spaces() {
        assert_eq!(
            AuthManager::normalize_username("  duncan tourolle  "),
            "duncan tourolle"
        );
    }

    /// Test URL normalization - real world case
    #[test]
    fn test_normalize_url_real_world_case() {
        let input = "jellyfin.tourolle.paris";
        let normalized = AuthManager::normalize_url(input).unwrap();

        assert_eq!(normalized, "https://jellyfin.tourolle.paris");
        assert!(normalized.starts_with("https://"));
        assert!(!normalized.ends_with('/'));
    }
}
