use crate::api::api_types::PoliciesResponse;
use crate::auth_token::AuthToken;
use crate::event::Event;
use crate::policy_loader::load_one_from_reader;
use crate::policy_types::ToolPolicyRecord;
use crate::policy_types::{ToolPolicy, ToolPolicyStatus};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tokio::task::JoinHandle;
use tokio::time;

#[derive(Debug)]
pub struct PolicyManager {
    auth_token: Arc<AuthToken>, // was String; now shared so background task sees updates
    pub policies: Arc<Mutex<Vec<ToolPolicyRecord>>>,
    refresh_handle: Option<JoinHandle<()>>, // background task
    refresh_interval: Duration,
    policies_url: String,
    pub on_policy_update: Arc<Event<Vec<ToolPolicyRecord>>>,
}

#[derive(Debug, thiserror::Error)]
pub enum PolicyManagerError {
    #[error("http error: {0}")]
    Http(#[from] reqwest::Error),
    #[error("policy load error: {0}")]
    PolicyLoad(#[from] crate::policy_loader::PolicyLoadError),
}

impl PolicyManager {
    pub fn new(
        auth_token: Arc<AuthToken>,
        refresh_interval_seconds: u64,
        base_url: String,
    ) -> Self {
        let base = base_url.trim_end_matches('/');
        let policies_url = format!("{}/api/policies", base);
        Self {
            auth_token,
            policies: Arc::new(Mutex::new(Vec::new())),
            refresh_handle: None,
            refresh_interval: Duration::from_secs(refresh_interval_seconds),
            policies_url,
            on_policy_update: Arc::new(Event::<Vec<ToolPolicyRecord>>::new()),
        }
    }

    pub fn with_url(mut self, policies_url: impl Into<String>) -> Self {
        self.policies_url = policies_url.into();
        self
    }

    pub fn start(&mut self) {
        let interval = self.refresh_interval;
        let access_token = Arc::clone(&self.auth_token);
        let policies_arc = Arc::clone(&self.policies);
        let policies_url = self.policies_url.clone();
        let on_update = Arc::clone(&self.on_policy_update);
        let handle = tokio::spawn(async move {
            // initial fetch immediately
            if let Err(e) = Self::fetch_once(
                &access_token.get(),
                &policies_url,
                policies_arc.clone(),
                on_update.clone(),
            )
            .await
            {
                tracing::warn!(error = %e, "initial policy fetch failed");
            }
            let mut ticker = time::interval(interval);
            loop {
                ticker.tick().await;
                if let Err(e) = Self::fetch_once(
                    &access_token.get(),
                    &policies_url,
                    policies_arc.clone(),
                    on_update.clone(),
                )
                .await
                {
                    tracing::warn!(error = %e, "policy fetch failed");
                }
            }
        });
        self.refresh_handle = Some(handle);
    }

    pub async fn stop(&mut self) {
        if let Some(handle) = self.refresh_handle.take() {
            handle.abort();
        }
    }

    pub async fn fetch_once(
        token: &str,
        policies_url: &str,
        policies_arc: Arc<Mutex<Vec<ToolPolicyRecord>>>,
        on_update: Arc<Event<Vec<ToolPolicyRecord>>>,
    ) -> Result<(), PolicyManagerError> {
        let client = reqwest::Client::new();
        let resp = client
            .get(policies_url)
            .bearer_auth(token)
            .send()
            .await?
            .error_for_status()?;
        let payload: PoliciesResponse = resp.json().await?;

        let mut records: Vec<ToolPolicyRecord> = Vec::with_capacity(payload.policies.len());
        for p in payload.policies {
            let policy_json = &p.current_version.body;

            let tool_policy: ToolPolicy = match load_one_from_reader(policy_json.as_bytes()) {
                Ok(v) => v,
                Err(e) => {
                    tracing::error!(error = %e, policy_name = %p.name, "failed to load policy");
                    continue;
                }
            };

            let status: ToolPolicyStatus = match p.status.parse() {
                Ok(v) => v,
                Err(e) => {
                    tracing::error!(error = %e, policy_name = %p.name, "failed to parse policy status");
                    continue;
                }
            };

            records.push(ToolPolicyRecord {
                id: p.id.clone(),
                name: p.name.clone(),
                current_version_id: p.current_version_id.clone(),
                policy: tool_policy,
                status,
            });
        }
        let mut guard = policies_arc.lock().unwrap();
        *guard = records.clone();
        on_update.emit(&records);

        Ok(())
    }

    /// Convenience: produce Vec<ToolPolicy> for PolicyEngine from current records
    pub fn tool_policies(&self) -> Vec<ToolPolicy> {
        let guard = self.policies.lock().unwrap();
        guard.iter().map(|r| r.policy.clone()).collect()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use axum::http::StatusCode;
    use axum::routing::get;
    use axum::{Json, Router};
    use pretty_assertions::assert_eq;
    use serde_json::{Value as JsonValue, json};

    async fn spawn_server_with_response(resp_status: StatusCode, body: JsonValue) -> String {
        let app = Router::new().route(
            "/policies",
            get({
                let body = body.clone();
                move || async move { (resp_status, Json(body.clone())) }
            }),
        );

        let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
            .await
            .expect("bind listener");
        let addr = listener.local_addr().unwrap();
        tokio::spawn(async move {
            axum::serve(listener, app).await.unwrap();
        });
        format!("http://{}:{}{}", addr.ip(), addr.port(), "/policies")
    }

    fn valid_policy_body(tool_name: &str) -> String {
        let policy = json!({
            "tool_name": tool_name,
            "rule": {
                "action": "allow",
                "when": {
                    "source": "args",
                    "matches": {
                        "key": "amount",
                        "op": "lte",
                        "value": 1000
                    }
                }
            }
        });
        policy.to_string()
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn fetch_once_success_updates_state_and_emits_event() {
        let body_str = valid_policy_body("dummy_tool");
        let payload = json!({
            "policies": [
                {
                    "id": "p1",
                    "name": "Policy One",
                    "currentVersionId": "v1",
                    "currentVersion": { "body": body_str },
                    "status": "ACTIVE"
                }
            ]
        });
        let url = spawn_server_with_response(StatusCode::OK, payload).await;

        let policies_arc: Arc<Mutex<Vec<ToolPolicyRecord>>> = Arc::new(Mutex::new(vec![]));
        let on_update: Arc<Event<Vec<ToolPolicyRecord>>> = Arc::new(Event::new());
        let captured: Arc<Mutex<Vec<Vec<ToolPolicyRecord>>>> = Arc::new(Mutex::new(vec![]));
        {
            let captured_cloned = captured.clone();
            on_update.subscribe(move |records: &Vec<ToolPolicyRecord>| {
                captured_cloned.lock().unwrap().push(records.clone());
            });
        }

        PolicyManager::fetch_once("token123", &url, policies_arc.clone(), on_update.clone())
            .await
            .expect("fetch_once should succeed");

        let guard = policies_arc.lock().unwrap();
        assert_eq!(guard.len(), 1);
        assert_eq!(guard[0].id, "p1");
        assert_eq!(guard[0].name, "Policy One");

        let events = captured.lock().unwrap();
        assert_eq!(events.len(), 1, "expected one on_update emission");
        assert_eq!(events[0].len(), 1);

        // tool_policies should map to ToolPolicy list
        let mgr = PolicyManager::new(Arc::new(AuthToken::new("t")), 60, "http://localhost".into());
        let cloned_mgr = mgr;
        // simulate manager holding the same policies
        {
            let mut m = cloned_mgr.policies.lock().unwrap();
            *m = guard.clone();
        }
        let tool_policies = cloned_mgr.tool_policies();
        assert_eq!(tool_policies.len(), 1);
        assert_eq!(tool_policies[0].tool_name, "dummy_tool");
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn fetch_once_skips_invalid_policy_bodies_but_keeps_valid() {
        let valid_body = valid_policy_body("tool_a");
        let invalid_body = "{ not valid json"; // will cause policy loader error
        let payload = json!({
            "policies": [
                {
                    "id": "p1",
                    "name": "Valid",
                    "currentVersionId": "v1",
                    "currentVersion": { "body": valid_body },
                    "status": "ACTIVE"
                },
                {
                    "id": "p2",
                    "name": "Invalid",
                    "currentVersionId": "v2",
                    "currentVersion": { "body": invalid_body },
                    "status": "ACTIVE"
                }
            ]
        });
        let url = spawn_server_with_response(StatusCode::OK, payload).await;

        let policies_arc: Arc<Mutex<Vec<ToolPolicyRecord>>> = Arc::new(Mutex::new(vec![]));
        let on_update: Arc<Event<Vec<ToolPolicyRecord>>> = Arc::new(Event::new());

        PolicyManager::fetch_once("token", &url, policies_arc.clone(), on_update.clone())
            .await
            .expect("fetch_once should succeed despite one invalid policy");

        let guard = policies_arc.lock().unwrap();
        assert_eq!(guard.len(), 1);
        assert_eq!(guard[0].name, "Valid");
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn fetch_once_http_error_is_propagated() {
        let payload = json!({ "policies": [] });
        let url = spawn_server_with_response(StatusCode::INTERNAL_SERVER_ERROR, payload).await;

        let policies_arc: Arc<Mutex<Vec<ToolPolicyRecord>>> = Arc::new(Mutex::new(vec![]));
        let on_update: Arc<Event<Vec<ToolPolicyRecord>>> = Arc::new(Event::new());

        let err = PolicyManager::fetch_once("token", &url, policies_arc, on_update)
            .await
            .expect_err("should return an HTTP error");

        // Ensure it's the HTTP variant (from reqwest)
        let _ = err; // No direct pattern match needed; reaching here is sufficient
    }
}
