use crate::kind::DecisionListKind;
use crate::sdk::api_key::KondutoApiKey;
use crate::sdk::endpoints::Endpoints::{
    CreateDecisionListEntryV1, CreateOrderV1, UpdateDecisionListEntryV1,
};
use crate::supports::tests::FixtureLoader;
use crate::{DecisionEntryType, KondutoClient};
use reqwest::Url;
use serde_json::json;
use wiremock::matchers::{body_partial_json, header, method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};

pub struct TestContext {
    pub mock_server: MockServer,
    pub client: KondutoClient,
    pub api_key: KondutoApiKey,
}

impl TestContext {
    pub async fn new() -> Self {
        let mock_server = MockServer::start().await;
        let api_key = KondutoApiKey::new("test_api_key").expect("Invalid test API key");
        let url = Url::parse(&mock_server.uri()).expect("Invalid mock server URL");
        let client = KondutoClient::with_base_url(api_key.clone(), url);

        let ctx = Self {
            mock_server,
            client,
            api_key,
        };

        Self::mock_create_blocklist_entry(&ctx).await;
        Self::mock_get_blocklist_entry(&ctx).await;
        Self::mock_updated_blocklist_entry(&ctx).await;
        Self::mock_delete_blocklist_entry(&ctx).await;
        Self::mock_approve_order_response(&ctx).await;
        Self::mock_declined_order_response_by_triggered_rules(&ctx).await;
        Self::mock_declined_order_response_by_triggered_decision_list(&ctx).await;
        ctx
    }

    async fn mock_create_blocklist_entry(ctx: &TestContext) {
        let response_body = FixtureLoader::load("decision_list/201_created");
        let mock = Mock::given(method("POST"))
            .and(path(
                CreateDecisionListEntryV1(DecisionListKind::Blocklist, DecisionEntryType::Email)
                    .to_string()
                    .as_str(),
            ))
            .and(header("Authorization", ctx.expected_auth_header().as_str()))
            .respond_with(Self::created_response(response_body));
        ctx.register_mock(mock).await;
    }

    async fn mock_updated_blocklist_entry(ctx: &TestContext) {
        let response_body = FixtureLoader::load("decision_list/200_updated");
        let mock = Mock::given(method("PUT"))
            .and(path(
                UpdateDecisionListEntryV1(
                    DecisionListKind::Blocklist,
                    DecisionEntryType::Email,
                    "test@mail.com".to_string(),
                )
                .to_string()
                .as_str(),
            ))
            .and(header("Authorization", ctx.expected_auth_header().as_str()))
            .respond_with(Self::success_response(response_body));
        ctx.register_mock(mock).await;
    }

    async fn mock_get_blocklist_entry(ctx: &TestContext) {
        let response_body = FixtureLoader::load("decision_list/200_fetch");
        let mock = Mock::given(method("GET"))
            .and(path(
                UpdateDecisionListEntryV1(
                    DecisionListKind::Blocklist,
                    DecisionEntryType::Email,
                    "test@mail.com".to_string(),
                )
                .to_string()
                .as_str(),
            ))
            .and(header("Authorization", ctx.expected_auth_header().as_str()))
            .respond_with(Self::success_response(response_body));
        ctx.register_mock(mock).await;
    }

    async fn mock_delete_blocklist_entry(ctx: &TestContext) {
        let response_body = FixtureLoader::load("decision_list/200_delete");
        let mock = Mock::given(method("DELETE"))
            .and(path(
                UpdateDecisionListEntryV1(
                    DecisionListKind::Blocklist,
                    DecisionEntryType::Email,
                    "test@mail.com".to_string(),
                )
                .to_string()
                .as_str(),
            ))
            .and(header("Authorization", ctx.expected_auth_header().as_str()))
            .respond_with(Self::success_response(response_body));
        ctx.register_mock(mock).await;
    }

    async fn mock_approve_order_response(ctx: &TestContext) {
        let response_body = FixtureLoader::load("orders/200_analyze_approve");
        let mock = Mock::given(method("POST"))
            .and(path(CreateOrderV1.to_string().as_str()))
            .and(header("Authorization", ctx.expected_auth_header().as_str()))
            .and(body_partial_json(json!({"total_amount": 100.0})))
            .respond_with(Self::success_response(response_body));
        ctx.register_mock(mock).await;
    }

    async fn mock_declined_order_response_by_triggered_rules(ctx: &TestContext) {
        let response_body = FixtureLoader::load("orders/200_analyze_decline_triggered_rules");
        let mock = Mock::given(method("POST"))
            .and(path(CreateOrderV1.to_string().as_str()))
            .and(header("Authorization", ctx.expected_auth_header().as_str()))
            .and(body_partial_json(json!({"total_amount": 151.0})))
            .respond_with(Self::success_response(response_body));
        ctx.register_mock(mock).await;
    }

    async fn mock_declined_order_response_by_triggered_decision_list(ctx: &TestContext) {
        let response_body =
            FixtureLoader::load("orders/200_analyze_decline_triggered_decision_list");
        let mock = Mock::given(method("POST"))
            .and(path(CreateOrderV1.to_string().as_str()))
            .and(header("Authorization", ctx.expected_auth_header().as_str()))
            .and(body_partial_json(
                json!({"customer": {"email": "declined@mail.com"}}),
            ))
            .respond_with(Self::success_response(response_body));
        ctx.register_mock(mock).await;
    }

    pub fn expected_auth_header(&self) -> String {
        self.api_key.as_b64()
    }

    pub async fn register_mock(&self, mock: Mock) {
        mock.mount(&self.mock_server).await;
    }

    pub fn success_response(body: String) -> ResponseTemplate {
        ResponseTemplate::new(200).set_body_string(body)
    }

    pub fn created_response(body: String) -> ResponseTemplate {
        ResponseTemplate::new(201).set_body_string(body)
    }

    #[allow(dead_code)]
    pub fn bad_request_response(body: String) -> ResponseTemplate {
        ResponseTemplate::new(400).set_body_string(body)
    }

    #[allow(dead_code)]
    pub fn unauthorized_response(body: String) -> ResponseTemplate {
        ResponseTemplate::new(401).set_body_string(body)
    }

    #[allow(dead_code)]
    pub fn not_found_response(body: String) -> ResponseTemplate {
        ResponseTemplate::new(404).set_body_string(body)
    }

    #[allow(dead_code)]
    pub fn server_error_response(body: String) -> ResponseTemplate {
        ResponseTemplate::new(500).set_body_string(body)
    }
}
