use serde::{Deserialize, Serialize};
use uuid::Uuid;
use std::collections::HashMap;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestContext {
    pub tenant_id: Uuid,
    pub user_id: Uuid,
    pub access_token: String,
    pub refresh_token: String,
    pub base_url: String,
    pub created_resources: HashMap<String, Vec<CreatedResource>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreatedResource {
    pub id: Uuid,
    pub name: String,
    pub resource_type: String,
}

impl TestContext {
    pub fn new() -> Self {
        Self {
            tenant_id: Uuid::nil(),
            user_id: Uuid::nil(),
            access_token: String::new(),
            refresh_token: String::new(),
            base_url: std::env::var("ERP_API_URL")
                .unwrap_or_else(|_| "http://localhost:3000".to_string()),
            created_resources: HashMap::new(),
        }
    }

    pub fn store_resource(&mut self, resource_type: &str, id: Uuid, name: &str) {
        self.created_resources
            .entry(resource_type.to_string())
            .or_insert_with(Vec::new)
            .push(CreatedResource {
                id,
                name: name.to_string(),
                resource_type: resource_type.to_string(),
            });
    }

    pub fn get_resource(&self, resource_type: &str, name: &str) -> Option<Uuid> {
        self.created_resources
            .get(resource_type)
            .and_then(|resources| {
                resources
                    .iter()
                    .find(|r| r.name == name)
                    .map(|r| r.id)
            })
    }

    pub fn auth_header(&self) -> String {
        format!("Bearer {}", self.access_token)
    }
}

impl Default for TestContext {
    fn default() -> Self {
        Self::new()
    }
}

pub struct HttpClient {
    base_url: String,
}

impl HttpClient {
    pub fn new(base_url: &str) -> Self {
        Self {
            base_url: base_url.to_string(),
        }
    }

    pub async fn get(&self, path: &str, token: Option<&str>) -> TestResponse {
        let client = reqwest::Client::new();
        let mut req = client.get(format!("{}{}", self.base_url, path));
        if let Some(t) = token {
            req = req.header("Authorization", format!("Bearer {}", t));
        }
        req.send().await.into()
    }

    pub async fn post<T: Serialize>(&self, path: &str, body: &T, token: Option<&str>) -> TestResponse {
        let client = reqwest::Client::new();
        let mut req = client.post(format!("{}{}", self.base_url, path));
        req = req.header("Content-Type", "application/json");
        if let Some(t) = token {
            req = req.header("Authorization", format!("Bearer {}", t));
        }
        req.json(body).send().await.into()
    }

    pub async fn put<T: Serialize>(&self, path: &str, body: &T, token: Option<&str>) -> TestResponse {
        let client = reqwest::Client::new();
        let mut req = client.put(format!("{}{}", self.base_url, path));
        req = req.header("Content-Type", "application/json");
        if let Some(t) = token {
            req = req.header("Authorization", format!("Bearer {}", t));
        }
        req.json(body).send().await.into()
    }

    pub async fn delete(&self, path: &str, token: Option<&str>) -> TestResponse {
        let client = reqwest::Client::new();
        let mut req = client.delete(format!("{}{}", self.base_url, path));
        if let Some(t) = token {
            req = req.header("Authorization", format!("Bearer {}", t));
        }
        req.send().await.into()
    }
}

#[derive(Debug)]
pub struct TestResponse {
    pub status: u16,
    pub body: serde_json::Value,
    pub elapsed_ms: u64,
}

impl From<reqwest::Response> for TestResponse {
    fn from(resp: reqwest::Response) -> Self {
        let status = resp.status().as_u16();
        let body = resp.json().unwrap_or(serde_json::json!({}));
        Self {
            status,
            body,
            elapsed_ms: 0,
        }
    }
}

pub async fn login(client: &HttpClient, username: &str, password: &str) -> Result<(String, String), String> {
    #[derive(Serialize)]
    struct LoginReq { username: String, password: String }

    #[derive(Deserialize)]
    struct LoginResp {
        data: LoginData
    }
    #[derive(Deserialize)]
    struct LoginData {
        access_token: String,
        refresh_token: String,
    }

    let resp = client.post("/api/v1/auth/login", &LoginReq {
        username: username.to_string(),
        password: password.to_string(),
    }, None).await;

    if resp.status == 200 {
        let data: LoginResp = serde_json::from_value(resp.body)
            .map_err(|e| format!("parse error: {}", e))?;
        Ok((data.data.access_token, data.data.refresh_token))
    } else {
        Err(format!("login failed: {} - {:?}", resp.status, resp.body))
    }
}

pub async fn refresh_token(client: &HttpClient, refresh_token: &str) -> Result<(String, String), String> {
    #[derive(Serialize)]
    struct RefreshReq { refresh_token: String }

    #[derive(Deserialize)]
    struct RefreshResp { data: RefreshData }
    #[derive(Deserialize)]
    struct RefreshData {
        access_token: String,
        refresh_token: String,
    }

    let resp = client.post("/api/v1/auth/refresh", &RefreshReq {
        refresh_token: refresh_token.to_string()
    }, None).await;

    if resp.status == 200 {
        let data: RefreshResp = serde_json::from_value(resp.body)
            .map_err(|e| format!("parse error: {}", e))?;
        Ok((data.data.access_token, data.data.refresh_token))
    } else {
        Err(format!("refresh failed: {} - {:?}", resp.status, resp.body))
    }
}

pub fn validate_uuid(id: &str) -> bool {
    Uuid::parse_str(id).is_ok()
}

pub fn validate_email(email: &str) -> bool {
    email.contains('@') && email.contains('.')
}

pub fn validate_pagination(body: &serde_json::Value) -> Option<(u64, u64, u64)> {
    let data = body.get("data")?;
    let total = data.get("total")?.as_u64()?;
    let page = data.get("page")?.as_u64()?;
    let page_size = data.get("page_size")?.as_u64()?;
    Some((total, page, page_size))
}