use anyhow::{Result, anyhow};
use chrono::{DateTime, Duration, Utc};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
pub struct DeviceCodeResponse {
    pub device_code: String,
    pub user_code: String,
    pub expires_in: u64,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct TokenResponse {
    pub access_token: String,
    #[serde(default)]
    pub refresh_token: Option<String>,
    #[serde(default)]
    pub expires_in: Option<u64>,
    #[serde(default)]
    pub refresh_expires_in: Option<u64>,
}

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

#[derive(Debug)]
pub struct DeviceCodeFlow {
    client: reqwest::Client,
    base_url: String,
    device_code: Option<String>,
    user_code: Option<String>,
    expires_at: Option<DateTime<Utc>>,
}

impl DeviceCodeFlow {
    pub fn new(base_url: String) -> Self {
        Self {
            client: reqwest::Client::new(),
            base_url,
            device_code: None,
            user_code: None,
            expires_at: None,
        }
    }

    pub async fn initiate_device_flow(&mut self) -> Result<String> {
        let url = format!("{}/api/v1/device", self.base_url);

        let response = self
            .client
            .post(&url)
            .header("Content-Type", "application/json")
            .send()
            .await?;

        if !response.status().is_success() {
            return Err(anyhow!(
                "Failed to initiate device flow: {}",
                response.status()
            ));
        }

        let device_response: DeviceCodeResponse = response.json().await?;

        self.device_code = Some(device_response.device_code.clone());
        self.user_code = Some(device_response.user_code.clone());
        self.expires_at = Some(Utc::now() + Duration::seconds(device_response.expires_in as i64));

        Ok(device_response.user_code)
    }

    pub async fn poll_for_authorization(&self) -> Result<Option<TokenResponse>> {
        let device_code = self
            .device_code
            .as_ref()
            .ok_or_else(|| anyhow!("No device code available"))?;

        let url = format!(
            "{}/api/v1/device?device_code={}",
            self.base_url, device_code
        );

        let response = self.client.get(&url).send().await?;

        match response.status().as_u16() {
            200 => {
                let text = response.text().await?;

                if let Ok(token_response) = serde_json::from_str::<TokenResponse>(&text) {
                    if token_response.access_token.is_empty() {
                        // No access_token means it parsed a pending response
                        Err(anyhow!("Unexpected response format"))
                    } else {
                        Ok(Some(token_response))
                    }
                } else if let Ok(pending_response) = serde_json::from_str::<PendingResponse>(&text)
                {
                    if pending_response.error == "authorization_pending" {
                        Ok(None)
                    } else {
                        Err(anyhow!("Authorization error: {}", pending_response.error))
                    }
                } else {
                    Err(anyhow!("Unexpected response format"))
                }
            }
            404 => Err(anyhow!("Device code not found or expired")),
            _ => Err(anyhow!("Polling failed: {}", response.status())),
        }
    }

    pub fn format_user_code(&self) -> Option<String> {
        self.user_code.as_ref().map(|code| {
            if code.len() == 8 {
                format!("{}-{}", &code[0..4], &code[4..8])
            } else {
                code.clone()
            }
        })
    }

    pub fn time_remaining(&self) -> Option<Duration> {
        self.expires_at.map(|expires| {
            let remaining = expires - Utc::now();
            if remaining > Duration::zero() {
                remaining
            } else {
                Duration::zero()
            }
        })
    }
}

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

    #[tokio::test]
    async fn test_initiate_device_flow_success() {
        let mut server = mockito::Server::new_async().await;
        let mock = server
            .mock("POST", "/api/v1/device")
            .with_status(200)
            .with_header("content-type", "application/json")
            .with_body(r#"{"device_code":"dev123","user_code":"ABCD1234","expires_in":600}"#)
            .create_async()
            .await;

        let mut flow = DeviceCodeFlow::new(server.url());
        let user_code = flow.initiate_device_flow().await.unwrap();
        assert_eq!(user_code, "ABCD1234");
        assert_eq!(flow.device_code.as_deref(), Some("dev123"));
        assert!(flow.expires_at.is_some());
        mock.assert_async().await;
    }

    #[tokio::test]
    async fn test_initiate_device_flow_server_error() {
        let mut server = mockito::Server::new_async().await;
        let mock = server
            .mock("POST", "/api/v1/device")
            .with_status(500)
            .create_async()
            .await;

        let mut flow = DeviceCodeFlow::new(server.url());
        let result = flow.initiate_device_flow().await;
        assert!(result.is_err());
        mock.assert_async().await;
    }

    #[tokio::test]
    async fn test_poll_for_authorization_pending() {
        let mut server = mockito::Server::new_async().await;
        let mock = server
            .mock("GET", "/api/v1/device?device_code=dev123")
            .with_status(200)
            .with_header("content-type", "application/json")
            .with_body(r#"{"error":"authorization_pending"}"#)
            .create_async()
            .await;

        let mut flow = DeviceCodeFlow::new(server.url());
        flow.device_code = Some("dev123".to_string());
        let result = flow.poll_for_authorization().await.unwrap();
        assert!(result.is_none());
        mock.assert_async().await;
    }

    #[tokio::test]
    async fn test_poll_for_authorization_success() {
        let mut server = mockito::Server::new_async().await;
        let mock = server
            .mock("GET", "/api/v1/device?device_code=dev123")
            .with_status(200)
            .with_header("content-type", "application/json")
            .with_body(r#"{"access_token":"tok_secret_abc","refresh_token":"ref_tok","expires_in":3600,"refresh_expires_in":2592000,"token_type":"Bearer"}"#)
            .create_async()
            .await;

        let mut flow = DeviceCodeFlow::new(server.url());
        flow.device_code = Some("dev123".to_string());
        let result = flow.poll_for_authorization().await.unwrap();
        let token_response = result.unwrap();
        assert_eq!(token_response.access_token, "tok_secret_abc");
        assert_eq!(token_response.refresh_token.as_deref(), Some("ref_tok"));
        assert_eq!(token_response.expires_in, Some(3600));
        mock.assert_async().await;
    }

    #[tokio::test]
    async fn test_poll_for_authorization_expired() {
        let mut server = mockito::Server::new_async().await;
        let mock = server
            .mock("GET", "/api/v1/device?device_code=dev123")
            .with_status(404)
            .create_async()
            .await;

        let mut flow = DeviceCodeFlow::new(server.url());
        flow.device_code = Some("dev123".to_string());
        let result = flow.poll_for_authorization().await;
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("expired"));
        mock.assert_async().await;
    }

    #[tokio::test]
    async fn test_poll_without_device_code_errors() {
        let server = mockito::Server::new_async().await;
        let flow = DeviceCodeFlow::new(server.url());
        let result = flow.poll_for_authorization().await;
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("No device code"));
    }

    #[test]
    fn test_format_user_code_8_chars() {
        let mut flow = DeviceCodeFlow::new("http://localhost".to_string());
        flow.user_code = Some("ABCD1234".to_string());
        assert_eq!(flow.format_user_code(), Some("ABCD-1234".to_string()));
    }

    #[test]
    fn test_format_user_code_other_length() {
        let mut flow = DeviceCodeFlow::new("http://localhost".to_string());
        flow.user_code = Some("ABC".to_string());
        assert_eq!(flow.format_user_code(), Some("ABC".to_string()));
    }

    #[test]
    fn test_format_user_code_none() {
        let flow = DeviceCodeFlow::new("http://localhost".to_string());
        assert_eq!(flow.format_user_code(), None);
    }

    #[test]
    fn test_time_remaining_future() {
        let mut flow = DeviceCodeFlow::new("http://localhost".to_string());
        flow.expires_at = Some(Utc::now() + Duration::minutes(5));
        let remaining = flow.time_remaining().unwrap();
        assert!(remaining > Duration::zero());
    }

    #[test]
    fn test_time_remaining_past() {
        let mut flow = DeviceCodeFlow::new("http://localhost".to_string());
        flow.expires_at = Some(Utc::now() - Duration::minutes(5));
        let remaining = flow.time_remaining().unwrap();
        assert_eq!(remaining, Duration::zero());
    }

    #[test]
    fn test_time_remaining_not_set() {
        let flow = DeviceCodeFlow::new("http://localhost".to_string());
        assert!(flow.time_remaining().is_none());
    }
}
