use crate::extract::SESSION_COOKIE;
use crate::EternaltwinSystem;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Redirect, Response};
use axum::{Extension, Form};
use axum_extra::extract::cookie::Cookie;
use axum_extra::extract::CookieJar;
use eternaltwin_core::auth::{RegisterWithUsernameOptions, RegisterWithVerifiedEmailOptions};
use eternaltwin_core::password::Password;
use eternaltwin_core::types::AnyError;
use eternaltwin_core::user::{UserDisplayName, Username};
use serde::{Deserialize, Serialize};
use thiserror::Error;

#[derive(Debug, Error)]
pub(crate) enum RegisterError {
  #[error("inner error")]
  Inner(#[from] AnyError),
}

impl IntoResponse for RegisterError {
  fn into_response(self) -> Response {
    let (code, msg) = match self {
      Self::Inner(_) => (StatusCode::INTERNAL_SERVER_ERROR, "internal server error".to_string()),
    };
    (code, msg).into_response()
  }
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(untagged)]
pub(crate) enum RegisterBody {
  VerifiedEmail(RegisterWithVerifiedEmailBody),
  Username(RegisterWithUsernameBody),
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub(crate) struct RegisterWithVerifiedEmailBody {
  pub email_token: String,
  pub display_name: UserDisplayName,
  pub password: String,
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub(crate) struct RegisterWithUsernameBody {
  pub username: Username,
  pub display_name: UserDisplayName,
  pub password: String,
}

pub(crate) async fn register(
  Extension(api): Extension<EternaltwinSystem>,
  jar: CookieJar,
  Form(body): Form<RegisterBody>,
) -> Result<(CookieJar, Redirect), RegisterError> {
  let user_and_session = match body {
    RegisterBody::VerifiedEmail(body) => {
      api
        .auth
        .as_ref()
        .register_with_verified_email(&RegisterWithVerifiedEmailOptions {
          email_token: body.email_token,
          display_name: body.display_name,
          password: Password::from(body.password.as_str()),
        })
        .await?
    }
    RegisterBody::Username(body) => {
      api
        .auth
        .as_ref()
        .register_with_username(&RegisterWithUsernameOptions {
          username: body.username,
          display_name: body.display_name,
          password: Password::from(body.password.as_str()),
        })
        .await?
    }
  };

  let cookie = Cookie::build(SESSION_COOKIE, user_and_session.session.id.to_string())
    .path("/")
    .http_only(true)
    .finish();
  Ok((jar.add(cookie), Redirect::to("/")))
}
