use std::sync::LazyLock;

use {
    axum::{
        Json,
        response::{IntoResponse, Response},
    },
    http::StatusCode,
    icann_rdap_common::{
        media_types::RDAP_MEDIA_TYPE,
        prelude::ToResponse,
        response::{RdapResponse, Rfc9083Error},
    },
    tracing::warn,
};

pub static NOT_FOUND: LazyLock<RdapResponse> = LazyLock::new(|| {
    Rfc9083Error::response_obj()
        .error_code(404)
        .title("Resource Not Found")
        .description_entry("The queried resource was not found.")
        .build()
        .to_response()
});
pub static NOT_IMPLEMENTED: LazyLock<RdapResponse> = LazyLock::new(|| {
    Rfc9083Error::response_obj()
        .error_code(501)
        .title("Feature Not Implemented")
        .description_entry(
            "The feature being requested is not implemented or deployed by this server.",
        )
        .build()
        .to_response()
});
pub static BAD_REQUEST: LazyLock<RdapResponse> = LazyLock::new(|| {
    Rfc9083Error::response_obj()
        .error_code(400)
        .title("Bad Request")
        .description_entry("The request is malformed and cannot be processed.")
        .build()
        .to_response()
});

pub(crate) const RDAP_HEADERS: [(&str, &str); 1] = [("content-type", RDAP_MEDIA_TYPE)];

pub(crate) trait ResponseUtil {
    fn status_code(&self) -> StatusCode;
    fn first_notice_link_href(&self) -> Option<&str>;
    fn response(&self) -> Response;
}

impl ResponseUtil for RdapResponse {
    fn status_code(&self) -> StatusCode {
        if let Self::ErrorResponse(rdap_error) = self {
            StatusCode::from_u16(rdap_error.error_code())
                .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)
        } else {
            StatusCode::OK
        }
    }

    fn first_notice_link_href(&self) -> Option<&str> {
        if let Self::ErrorResponse(rdap_error) = self {
            let notices = rdap_error.common.notices.as_ref()?;
            let first_notice = notices.first()?;
            let links = first_notice.0.links.as_ref()?;
            let first_link = links.first()?;
            let href = first_link.href.as_ref()?;
            Some(href)
        } else {
            None
        }
    }

    fn response(&self) -> Response {
        let status_code = self.status_code();
        match status_code {
            StatusCode::MULTIPLE_CHOICES
            | StatusCode::FOUND
            | StatusCode::SEE_OTHER
            | StatusCode::USE_PROXY
            | StatusCode::TEMPORARY_REDIRECT
            | StatusCode::PERMANENT_REDIRECT
            | StatusCode::NOT_MODIFIED => {
                let href = self.first_notice_link_href();
                if let Some(href) = href {
                    let headers: [(&str, &str); 2] = [RDAP_HEADERS[0], ("location", href)];
                    (status_code, headers, Json(self)).into_response()
                } else {
                    warn!("redirect does not have an href to use for location header.");
                    (status_code, RDAP_HEADERS, Json(self)).into_response()
                }
            }
            _ => (status_code, RDAP_HEADERS, Json(self)).into_response(),
        }
    }
}

#[cfg(test)]
#[allow(non_snake_case)]
mod tests {

    use {
        axum::response::IntoResponse,
        http::StatusCode,
        icann_rdap_common::{
            prelude::ToResponse,
            response::{Domain, Link, Notice, NoticeOrRemark, Rfc9083Error},
        },
    };

    use crate::rdap::response::{NOT_FOUND, NOT_IMPLEMENTED, ResponseUtil};

    #[test]
    fn GIVEN_non_error_WHEN_exec_response_THEN_status_code_is_200() {
        // GIVEN
        let domain = Domain::builder()
            .ldh_name("foo.example")
            .build()
            .to_response();

        // WHEN
        let actual = domain.response();

        // THEN
        assert_eq!(actual.into_response().status(), StatusCode::OK);
    }

    #[test]
    fn GIVEN_not_found_WHEN_exec_response_THEN_status_code_is_501() {
        // GIVEN

        // WHEN
        let actual = NOT_FOUND.response();

        // THEN
        assert_eq!(actual.into_response().status(), StatusCode::NOT_FOUND);
    }

    #[test]
    fn GIVEN_not_implemented_WHEN_exec_response_THEN_status_code_is_500() {
        // GIVEN

        // WHEN
        let actual = NOT_IMPLEMENTED.response();

        // THEN
        assert_eq!(actual.into_response().status(), StatusCode::NOT_IMPLEMENTED);
    }

    #[test]
    fn GIVEN_rdap_response_with_first_link_WHEN_get_first_link_href_THEN_href_returned() {
        // GIVEN
        let given = Rfc9083Error::response_obj()
            .error_code(307)
            .notice(Notice(
                NoticeOrRemark::builder()
                    .links(vec![
                        Link::builder()
                            .href("https://other.example.com")
                            .value("https://other.example.com")
                            .rel("related")
                            .build(),
                    ])
                    .build(),
            ))
            .build()
            .to_response();

        // WHEN
        let actual = given.first_notice_link_href();

        // THEN
        assert_eq!(actual.expect("no href"), "https://other.example.com");
    }

    #[test]
    fn test_error_response_with_invalid_status_code_does_not_panic() {
        // GIVEN an error response whose code is not a valid HTTP status code
        let given = Rfc9083Error::response_obj()
            .error_code(1000)
            .build()
            .to_response();

        // WHEN the response is built
        let actual = given.response();

        // THEN the status falls back to 500 instead of panicking
        assert_eq!(
            actual.into_response().status(),
            StatusCode::INTERNAL_SERVER_ERROR
        );
    }
}
