#![cfg(feature = "cookies")]

use std::convert::TryInto;
use std::sync::Arc;

use async_trait::async_trait;
use mendes::application::IntoResponse;
use mendes::cookies::{cookie, AppWithAeadKey, AppWithCookies, Key};
use mendes::http::header::{COOKIE, SET_COOKIE};
use mendes::http::request::Parts;
use mendes::http::{Request, Response, StatusCode};
use mendes::{handler, route, Application, Context};
use serde::{Deserialize, Serialize};

#[tokio::test]
async fn cookie() {
    let app = Arc::new(App {
        key: mendes::cookies::Key::new(&[
            0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
            24, 25, 26, 27, 28, 29, 30, 31,
        ]),
    });

    let rsp = App::handle(Context::new(app.clone(), path_request("/store"))).await;
    assert_eq!(rsp.status(), StatusCode::OK);
    let set = rsp.headers().get(SET_COOKIE).unwrap();
    let value = set.to_str().unwrap().split(';').next().unwrap();

    let mut req = path_request("/extract");
    req.headers_mut().insert(COOKIE, value.try_into().unwrap());
    let rsp = App::handle(Context::new(app, req)).await;
    assert_eq!(rsp.status(), StatusCode::OK);
    assert_eq!(rsp.into_body(), "user = 37");
}

fn path_request(path: &str) -> Request<()> {
    Request::builder()
        .uri(format!("https://example.com{path}"))
        .body(())
        .unwrap()
}

struct App {
    key: mendes::cookies::Key,
}

impl AppWithAeadKey for App {
    fn key(&self) -> &Key {
        &self.key
    }
}

#[async_trait]
impl Application for App {
    type RequestBody = ();
    type ResponseBody = String;
    type Error = Error;

    async fn handle(mut cx: Context<Self>) -> Response<Self::ResponseBody> {
        route!(match cx.path() {
            Some("store") => store,
            Some("extract") => extract,
        })
    }
}

#[handler(GET)]
async fn extract(app: &App, req: &http::request::Parts) -> Result<Response<String>, Error> {
    let session = app.cookie::<Session>(&req.headers).unwrap();
    Ok(Response::builder()
        .status(StatusCode::OK)
        .body(format!("user = {}", session.user))
        .unwrap())
}

#[handler(GET)]
async fn store(app: &App) -> Result<Response<String>, Error> {
    let session = Session { user: 37 };
    Ok(Response::builder()
        .status(StatusCode::OK)
        .header(SET_COOKIE, app.set_cookie_header(Some(session)).unwrap())
        .body("Hello, world".into())
        .unwrap())
}

#[cookie]
#[derive(Deserialize, Serialize)]
struct Session {
    user: i32,
}

#[derive(Debug)]
enum Error {
    Mendes(mendes::Error),
}

impl From<mendes::Error> for Error {
    fn from(e: mendes::Error) -> Self {
        Error::Mendes(e)
    }
}

impl From<&Error> for StatusCode {
    fn from(e: &Error) -> StatusCode {
        let Error::Mendes(e) = e;
        StatusCode::from(e)
    }
}

impl IntoResponse<App> for Error {
    fn into_response(self, _: &App, _: &Parts) -> Response<String> {
        let Error::Mendes(err) = self;
        Response::builder()
            .status(StatusCode::from(&err))
            .body(err.to_string())
            .unwrap()
    }
}
