use reqwest::blocking::Client;
use serde::Deserialize;

use crate::{
    CreateSessionInput, PayPluginError, PlatformClient, PlatformSession, PollSessionResult,
    SessionStatus,
};

#[derive(Debug, Deserialize)]
struct CreateSessionResponse {
    auth_session_id: String,
    poll_token: String,
    login_url: String,
    #[allow(dead_code)]
    expires_at: Option<String>,
    poll_interval_seconds: u64,
}

#[derive(Debug, Deserialize)]
struct PollSessionResponse {
    auth_session_id: String,
    view_state: Option<String>,
    status: Option<String>,
    license_token: Option<String>,
    reason: Option<String>,
}

pub struct ReqwestPlatformClient {
    base_url: String,
    client: Client,
}

impl ReqwestPlatformClient {
    pub fn new(base_url: impl Into<String>) -> Result<Self, PayPluginError> {
        let base_url = base_url.into();
        let parsed =
            url::Url::parse(&base_url).map_err(|e| PayPluginError::Platform(e.to_string()))?;
        let client = Client::builder()
            .https_only(parsed.scheme() == "https")
            .build()
            .map_err(|e| PayPluginError::Platform(e.to_string()))?;
        Ok(Self { base_url, client })
    }
}

impl PlatformClient for ReqwestPlatformClient {
    fn create_session(
        &self,
        input: &CreateSessionInput,
    ) -> Result<PlatformSession, PayPluginError> {
        let url = join_url(&self.base_url, "/api/plugin/auth-sessions")?;
        let mut device_id_payload = serde_json::json!({
            "product_id": input.product_id,
            "device_id": input.device_fingerprint,
            "client_type": input.client_type,
            "client_version": input.client_version,
        });
        if let Some(name) = &input.device_name {
            device_id_payload["device_name"] = serde_json::Value::String(name.clone());
        }
        let response = self
            .client
            .post(url)
            .json(&device_id_payload)
            .send()
            .map_err(map_send_err)?;
        let status = response.status();
        if !status.is_success() {
            if status.as_u16() == 404 {
                return Err(PayPluginError::Platform("product_not_found".into()));
            }
            if status.as_u16() == 409 {
                let body = response.text().unwrap_or_default();
                if body.contains("product_inactive") {
                    return Err(PayPluginError::Platform("product_inactive".into()));
                }
            }
            return Err(PayPluginError::Platform(format!("http_{}", status.as_u16())));
        }
        let parsed: CreateSessionResponse = response.json().map_err(map_send_err)?;
        Ok(PlatformSession {
            auth_session_id: parsed.auth_session_id,
            poll_token: parsed.poll_token,
            login_url: parsed.login_url,
            expires_at_epoch_millis: None,
            poll_interval_seconds: parsed.poll_interval_seconds,
        })
    }

    fn poll_session(
        &self,
        session: &PlatformSession,
    ) -> Result<PollSessionResult, PayPluginError> {
        let path = format!("/api/plugin/auth-sessions/{}", session.auth_session_id);
        let url = join_url(&self.base_url, &path)?;
        let response = self
            .client
            .get(url)
            .bearer_auth(&session.poll_token)
            .send()
            .map_err(map_send_err)?;
        if !response.status().is_success() {
            return Err(PayPluginError::Platform(format!(
                "http_{}",
                response.status().as_u16()
            )));
        }
        let parsed: PollSessionResponse = response.json().map_err(map_send_err)?;
        let status = if parsed.license_token.is_some() {
            SessionStatus::Authorized
        } else {
            match parsed.view_state.as_deref().or(parsed.status.as_deref()) {
                Some("authorized") | Some("collected") => SessionStatus::Authorized,
                Some("expired") => SessionStatus::Expired,
                Some("conflict_user") | Some("failed") => SessionStatus::Failed,
                _ => SessionStatus::PendingLogin,
            }
        };
        Ok(PollSessionResult {
            auth_session_id: parsed.auth_session_id,
            status,
            license_token: parsed.license_token,
            reason: parsed.reason,
        })
    }
}

fn join_url(base_url: &str, path: &str) -> Result<url::Url, PayPluginError> {
    let mut base = url::Url::parse(base_url).map_err(|e| PayPluginError::Platform(e.to_string()))?;
    base.set_path(path);
    Ok(base)
}

fn map_send_err(error: reqwest::Error) -> PayPluginError {
    if error.is_connect() || error.is_timeout() || error.is_request() {
        PayPluginError::Network
    } else {
        PayPluginError::Platform(error.to_string())
    }
}
