use std::sync::Arc;

use crate::data::{MockDefinition, MockServerHttpResponse, Pattern, RequestRequirements};
use crate::server::web::handlers::add_new_mock;
use crate::server::{start_server, MockServerState};
use crate::util::read_file;
use crate::{NameValuePair, YAMLMockDefinition};
use regex::Regex;
use std::fs;
use std::fs::read_dir;
use std::path::PathBuf;
use std::str::FromStr;
use tokio::time::Duration;

pub async fn start_standalone_server(
    port: u16,
    expose: bool,
    static_mock_dir_path: Option<PathBuf>,
    print_access_log: bool,
) -> Result<(), String> {
    let state = Arc::new(MockServerState::new());

    #[cfg(feature = "standalone")]
    static_mock_dir_path.map(|path| {
        read_static_mocks(path)
            .into_iter()
            .map(|d| map_to_mock_definition(d))
            .for_each(|static_mock| {
                add_new_mock(&state, static_mock, true).expect("cannot add static mock");
            })
    });

    start_server(port, expose, &state, None, print_access_log).await
}

#[cfg(feature = "standalone")]
fn read_static_mocks(path: PathBuf) -> Vec<YAMLMockDefinition> {
    let mut definitions = Vec::new();

    let paths = read_dir(path).expect("cannot list files in directory");
    for file_path in paths {
        let file_path = file_path.unwrap().path();
        if let Some(ext) = file_path.extension() {
            if !"yaml".eq(ext) && !"yml".eq(ext) {
                continue;
            }
        }

        log::info!(
            "Loading static mock file from '{}'",
            file_path.to_string_lossy()
        );
        let content = read_file(file_path).expect("cannot read from file");
        let content = String::from_utf8(content).expect("cannot convert file content");

        definitions.push(serde_yaml::from_str(&content).unwrap());
    }

    return definitions;
}

#[cfg(feature = "standalone")]
fn map_to_mock_definition(yaml_definition: YAMLMockDefinition) -> MockDefinition {
    MockDefinition {
        request: RequestRequirements {
            path: yaml_definition.when.path,
            path_contains: yaml_definition.when.path_contains,
            path_matches: to_pattern_vec(yaml_definition.when.path_matches),
            method: yaml_definition.when.method.map(|m| m.to_string()),
            headers: to_pair_vec(yaml_definition.when.header),
            header_exists: yaml_definition.when.header_exists,
            cookies: to_pair_vec(yaml_definition.when.cookie),
            cookie_exists: yaml_definition.when.cookie_exists,
            body: yaml_definition.when.body,
            json_body: yaml_definition.when.json_body,
            json_body_includes: yaml_definition.when.json_body_partial,
            body_contains: yaml_definition.when.body_contains,
            body_matches: to_pattern_vec(yaml_definition.when.body_matches),
            query_param_exists: yaml_definition.when.query_param_exists,
            query_param: to_pair_vec(yaml_definition.when.query_param),
            matchers: None,
        },
        response: MockServerHttpResponse {
            status: yaml_definition.then.status,
            headers: to_pair_vec(yaml_definition.then.header),
            body: yaml_definition.then.body.map(|b| b.into_bytes()),
            delay: yaml_definition.then.delay.map(|v| Duration::from_millis(v)),
        },
    }
}

#[cfg(feature = "standalone")]
fn to_pattern_vec(vec: Option<Vec<String>>) -> Option<Vec<Pattern>> {
    vec.map(|vec| {
        vec.iter()
            .map(|val| Pattern::from_regex(Regex::from_str(val).expect("cannot parse regex")))
            .collect()
    })
}

#[cfg(feature = "standalone")]
fn to_pair_vec(kvp: Option<Vec<NameValuePair>>) -> Option<Vec<(String, String)>> {
    kvp.map(|vec| vec.into_iter().map(|nvp| (nvp.name, nvp.value)).collect())
}
