use nowhub_plugin_rust::{
    BrowserOpener, CacheStore, CachedLicense, CheckResult, CheckSource, CreateSessionInput,
    FailureReason, LicenseVerifier, PayPlugin, PayPluginConfig, PayPluginError, PlatformClient,
    PlatformSession, PollSessionResult, SessionStatus,
};
use std::cell::RefCell;
use std::path::Path;

struct FakeClient {
    create_inputs: RefCell<Vec<CreateSessionInput>>,
    poll_results: RefCell<Vec<PollSessionResult>>,
}

impl PlatformClient for FakeClient {
    fn create_session(
        &self,
        input: &CreateSessionInput,
    ) -> Result<PlatformSession, PayPluginError> {
        self.create_inputs.borrow_mut().push(input.clone());
        Ok(PlatformSession {
            auth_session_id: "as_demo".to_string(),
            poll_token: "pt_demo".to_string(),
            login_url: "https://axis.example.test/authorize/as_demo".to_string(),
            expires_at_epoch_millis: None,
            poll_interval_seconds: 1,
        })
    }

    fn poll_session(
        &self,
        _session: &PlatformSession,
    ) -> Result<PollSessionResult, PayPluginError> {
        Ok(self.poll_results.borrow_mut().remove(0))
    }
}

struct FakeOpener;

impl BrowserOpener for FakeOpener {
    fn open(&self, _url: &str) -> Result<(), PayPluginError> {
        Ok(())
    }
}

struct MemoryCache {
    token: RefCell<Option<String>>,
}

impl CacheStore for MemoryCache {
    fn read(&self, _path: &Path) -> Result<Option<CachedLicense>, PayPluginError> {
        Ok(self
            .token
            .borrow()
            .clone()
            .map(|license_token| CachedLicense { license_token }))
    }

    fn write(&self, _path: &Path, license_token: &str) -> Result<(), PayPluginError> {
        *self.token.borrow_mut() = Some(license_token.to_string());
        Ok(())
    }
}

struct ExactVerifier {
    accepted_token: &'static str,
}

impl LicenseVerifier for ExactVerifier {
    fn verify(
        &self,
        license_token: &str,
        _tenant_id: &str,
        _product_id: &str,
        _device_fingerprint: &str,
    ) -> bool {
        license_token == self.accepted_token
    }
}

#[test]
fn returns_cached_authorization_when_cached_license_verifies() {
    let plugin = PayPlugin::new(
        PayPluginConfig::rust("tenant_axis", "1.0.0", "device_rust_1"),
        FakeClient {
            create_inputs: RefCell::new(vec![]),
            poll_results: RefCell::new(vec![]),
        },
        FakeOpener,
        MemoryCache {
            token: RefCell::new(Some("cached_token".to_string())),
        },
        ExactVerifier {
            accepted_token: "cached_token",
        },
    );

    assert_eq!(
        plugin.check("prod_writer"),
        CheckResult::Authorized {
            source: CheckSource::Cache
        }
    );
}

#[test]
fn creates_rust_session_and_caches_platform_license() {
    let client = FakeClient {
        create_inputs: RefCell::new(vec![]),
        poll_results: RefCell::new(vec![PollSessionResult {
            auth_session_id: "as_demo".to_string(),
            status: SessionStatus::Authorized,
            license_token: Some("platform_token".to_string()),
            reason: None,
        }]),
    };
    let cache = MemoryCache {
        token: RefCell::new(None),
    };
    let plugin = PayPlugin::new(
        PayPluginConfig::rust("tenant_axis", "1.0.0", "device_rust_1"),
        client,
        FakeOpener,
        cache,
        ExactVerifier {
            accepted_token: "cached_token",
        },
    );

    assert_eq!(
        plugin.check("prod_writer"),
        CheckResult::Authorized {
            source: CheckSource::Platform
        }
    );
}

#[test]
fn maps_cancelled_session_to_denial() {
    let plugin = PayPlugin::new(
        PayPluginConfig::rust("tenant_axis", "1.0.0", "device_rust_1"),
        FakeClient {
            create_inputs: RefCell::new(vec![]),
            poll_results: RefCell::new(vec![PollSessionResult {
                auth_session_id: "as_demo".to_string(),
                status: SessionStatus::Cancelled,
                license_token: None,
                reason: None,
            }]),
        },
        FakeOpener,
        MemoryCache {
            token: RefCell::new(None),
        },
        ExactVerifier {
            accepted_token: "cached_token",
        },
    );

    assert_eq!(
        plugin.check("prod_writer"),
        CheckResult::Denied {
            reason: FailureReason::UserCancelled
        }
    );
}
