use std::{
    sync::{Arc, Mutex},
    time::Duration,
};

use async_trait::async_trait;
use mockall::mock;

use crate::network::{
    access_token_source::AccessTokenSource,
    core_network_service::{CoreNetworkService, CoreNetworkServiceImpl},
    net_provider::NetProvider,
    network_error::NetworkError,
    network_manager::NetworkManagerImpl,
    request::{HttpMethod, Request, RequestBuilder},
    response::Response,
    url_provider::UrlProvider,
};

// MARK: - MockUrlProvider

mock! {
    #[derive(Clone)]
    pub UrlProvider {}

    impl UrlProvider for UrlProvider {
        fn apply_url_domains<'a>(
            &self,
            data_api_domain: Option<&'a str>,
            streaming_domain: Option<&'a str>,
            access_token_domain: Option<&'a str>,
        );

        fn make_tracking_url(&self) -> String;
        fn make_visitor_data_url(
            &self,
            visitor_code: &str,
            filter: &crate::types::RemoteVisitorDataFilter,
            is_unique_identifier: bool,
        ) -> String;
        fn make_remote_data_url(&self, key: &str) -> String;
        fn make_datafile_url<'a>(&self, environment: Option<&'a str>, timestamp: Option<u64>) -> String;

        fn make_real_time_url(&self) -> String;

        fn make_access_token_url(&self) -> String;

        fn site_code(&self) -> &str;
        fn configuration_domain(&self) -> Arc<String>;
        fn data_api_domain(&self) -> Arc<String>;
        fn streaming_domain(&self) -> Arc<String>;
        fn access_token_domain(&self) -> Arc<String>;
    }
}

// MARK: - MockNetProvider

#[derive(Clone)]
pub struct MockNetProvider {
    captured_requests: Arc<Mutex<Vec<Request>>>,
    responses: Arc<Mutex<Vec<Result<Response, NetworkError>>>>,
    captured_headers_to_read: Arc<Mutex<Vec<String>>>,
    call_count: Arc<Mutex<usize>>,
}

impl MockNetProvider {
    pub fn with_responses(responses: Vec<Result<Response, NetworkError>>) -> Self {
        Self {
            captured_requests: Arc::new(Mutex::new(Vec::new())),
            captured_headers_to_read: Arc::new(Mutex::new(Vec::new())),
            responses: Arc::new(Mutex::new(responses)),
            call_count: Arc::new(Mutex::new(0)),
        }
    }

    pub fn call_count(&self) -> usize {
        *self.call_count.lock().unwrap()
    }

    pub fn captured_requests(&self) -> Vec<Request> {
        self.captured_requests.lock().unwrap().clone()
    }

    pub fn captured_headers_to_read(&self) -> Vec<String> {
        self.captured_headers_to_read.lock().unwrap().clone()
    }
}

#[async_trait::async_trait]
impl NetProvider for MockNetProvider {
    async fn request(&self, request: Request, headers_to_read: &Option<Vec<&str>>) -> Result<Response, NetworkError> {
        let mut count = self.call_count.lock().unwrap();
        *count += 1;

        self.captured_requests.lock().unwrap().push(request.clone());

        if let Some(headers) = headers_to_read {
            for header in headers {
                self.captured_headers_to_read.lock().unwrap().push(header.to_string());
            }
        }

        let mut responses = self.responses.lock().unwrap();
        if !responses.is_empty() {
            responses.remove(0)
        } else {
            Ok(Response {
                status_code: 200,
                body: b"success".to_vec(),
                headers: Vec::new(),
            })
        }
    }
}

// MARK: - MockAccessTokenSource

#[derive(Clone)]
pub struct MockAccessTokenSource {
    tokens: Arc<Mutex<Vec<String>>>,
    discarded_tokens: Arc<Mutex<Vec<String>>>,
    should_fail: Arc<Mutex<bool>>,
}

impl MockAccessTokenSource {
    pub fn new() -> Self {
        Self {
            tokens: Arc::new(Mutex::new(vec!["valid_token".to_string()])),
            discarded_tokens: Arc::new(Mutex::new(Vec::new())),
            should_fail: Arc::new(Mutex::new(false)),
        }
    }

    pub fn with_tokens(tokens: Vec<String>) -> Self {
        Self {
            tokens: Arc::new(Mutex::new(tokens)),
            discarded_tokens: Arc::new(Mutex::new(Vec::new())),
            should_fail: Arc::new(Mutex::new(false)),
        }
    }

    pub fn set_should_fail(&self, should_fail: bool) {
        *self.should_fail.lock().unwrap() = should_fail;
    }

    pub fn discarded_tokens(&self) -> Vec<String> {
        self.discarded_tokens.lock().unwrap().clone()
    }
}

#[async_trait::async_trait]
impl AccessTokenSource for MockAccessTokenSource {
    async fn get_token(&self) -> Option<String> {
        return self.get_token_now();
    }

    fn get_token_now(&self) -> Option<String> {
        if *self.should_fail.lock().unwrap() {
            return None;
        }

        let mut tokens = self.tokens.lock().unwrap();
        if !tokens.is_empty() { Some(tokens.remove(0)) } else { None }
    }

    async fn discard_token(&self, token: String) {
        self.discarded_tokens.lock().unwrap().push(token);
    }
}

// MARK: - MockCoreNetwork

mock! {
    pub CoreNetwork {}

    #[async_trait]
    impl CoreNetworkService for CoreNetwork {
        async fn call(
            &self,
            request: Request,
            request_url: &str,
            headers_to_read: &Option<Vec<&'static str>>,
            attempt_count: usize,
            retry_delay: Duration,
        ) -> Result<Response, NetworkError>;
    }
}

// MARK: - NetworkManagerImpl

pub fn create_network_manager_net_provider(
    net_provider: Option<MockNetProvider>,
    url_provider: Option<MockUrlProvider>,
    access_token_source: Option<MockAccessTokenSource>,
) -> NetworkManagerImpl<CoreNetworkServiceImpl<MockNetProvider>, MockUrlProvider, MockAccessTokenSource> {
    NetworkManagerImpl::new(
        Arc::new(CoreNetworkServiceImpl {
            net_provider: net_provider.unwrap_or_else(|| MockNetProvider::with_responses(vec![])),
        }),
        Arc::new(url_provider.unwrap_or_else(|| MockUrlProvider::new())),
        access_token_source.unwrap_or_else(|| MockAccessTokenSource::new()),
        Duration::from_secs(10),
    )
}

pub fn create_network_manager_core_network(
    core_network: MockCoreNetwork,
    url_provider: Option<MockUrlProvider>,
    access_token_source: Option<MockAccessTokenSource>,
) -> NetworkManagerImpl<MockCoreNetwork, MockUrlProvider, MockAccessTokenSource> {
    NetworkManagerImpl::new(
        Arc::new(core_network),
        Arc::new(url_provider.unwrap_or_else(|| MockUrlProvider::new())),
        access_token_source.unwrap_or_else(|| MockAccessTokenSource::new()),
        Duration::from_secs(10),
    )
}

pub fn create_success_response() -> Response {
    Response {
        status_code: 200,
        body: "".as_bytes().to_vec(),
        headers: Vec::new(),
    }
}

pub fn create_success_response_params(body: &str, headers: Vec<(String, String)>) -> Response {
    Response {
        status_code: 200,
        body: body.as_bytes().to_vec(),
        headers: headers,
    }
}

pub fn create_status_code_response(status_code: u16) -> Response {
    Response {
        status_code,
        body: b"error response".to_vec(),
        headers: Vec::new(),
    }
}

// MARK: - RequestBuilder

pub fn create_simple_builder() -> RequestBuilder {
    RequestBuilder::default().method(HttpMethod::GET).url("https://api.example.com/test")
}
