use chrono::Utc;
use uuid::Uuid;

use crate::plugins::helpers::{build_public_auth_url_on_origin, session_with_server_version};
use athena_auth_core::User;
use athena_auth_core::{AuthContext, AuthResult};
use athena_auth_core::{
    AuthEmailRenderContext, build_auth_email_message, verification_email_for_context,
};
use athena_auth_core::{AuthError, CreateVerification, UpdateUser};
use athena_auth_core::{AuthSession, AuthUser, AuthVerification, DatabaseAdapter};

use super::types::*;
use super::{EmailVerificationConfig, StatusResponse};

pub(super) fn build_verification_url<DB: DatabaseAdapter>(
    ctx: &AuthContext<DB>,
    token: &str,
    callback_url: Option<&str>,
) -> String {
    let default_verify_url: String = ctx.config.public_auth_url("verify-email");
    let mut verify_url: String = default_verify_url;

    // If callbackURL is absolute, use its origin for the verification hop so
    // browser session cookies are set on the same origin as the UI (e.g. Next proxy).
    if let Some(callback) = callback_url
        && let Ok(parsed) = url::Url::parse(callback)
        && parsed.origin().is_tuple()
    {
        let origin: String = parsed.origin().ascii_serialization();
        verify_url = build_public_auth_url_on_origin(
            &origin,
            ctx.config.effective_public_base_path(),
            "verify-email",
        );
    }

    let mut out = format!("{}?token={}", verify_url, urlencoding::encode(token));
    if let Some(callback) = callback_url {
        out.push_str("&callbackURL=");
        out.push_str(&urlencoding::encode(callback));
    }
    out
}

pub(super) async fn send_verification_email_core<DB: DatabaseAdapter>(
    body: &SendVerificationEmailRequest,
    config: &EmailVerificationConfig,
    ctx: &AuthContext<DB>,
) -> AuthResult<StatusResponse> {
    // Check if user exists
    let user = ctx
        .database
        .get_user_by_email(&body.email)
        .await?
        .ok_or_else(|| AuthError::not_found("No user found with this email address"))?;

    // Check if user is already verified
    if user.email_verified() {
        return Err(AuthError::bad_request("Email is already verified"));
    }

    // Generate verification token
    let verification_token = format!("verify_{}", Uuid::new_v4());
    let expires_at = Utc::now() + config.verification_token_expiry;

    let create_verification = CreateVerification {
        identifier: body.email.to_string(),
        value: verification_token.clone(),
        expires_at,
    };

    ctx.database
        .create_verification(create_verification)
        .await?;

    let verification_url =
        build_verification_url(ctx, &verification_token, body.callback_url.as_deref());

    // Use custom sender if configured, otherwise fall back to EmailProvider
    if let Some(ref custom_sender) = config.send_verification_email {
        let wrapped = User::from(&user);
        if let Err(err) = custom_sender
            .send(&wrapped, &verification_url, &verification_token)
            .await
        {
            super::record_email_send_failure(
                ctx,
                Some(wrapped.id.as_str()),
                &body.email,
                "send_verification_email_endpoint",
                Some("custom_sender"),
                &err.to_string(),
                None,
            )
            .await;
            tracing::error!(
                recipient_email = %body.email,
                flow = "send_verification_email_endpoint",
                provider = "custom_sender",
                error = %err,
                "Failed to send verification email"
            );
            return Err(err);
        }
        tracing::info!(
            recipient_email = %body.email,
            user_id = %user.id(),
            flow = "send_verification_email_endpoint",
            provider = "custom_sender",
            "verification email sent"
        );
    } else if config.send_email_notifications {
        if ctx.email_provider.is_some() {
            let bodies = verification_email_for_context(
                ctx,
                &verification_url,
                AuthEmailRenderContext::new().with_user_id(user.id()),
            )
            .await;
            let message = match build_auth_email_message(ctx, &body.email, &bodies).await {
                Ok(message) => message,
                Err(error) => {
                    super::record_email_send_failure(
                        ctx,
                        Some(user.id()),
                        &body.email,
                        "send_verification_email_endpoint",
                        Some("email_provider"),
                        &error.to_string(),
                        error.metadata().cloned(),
                    )
                    .await;
                    return Err(athena_auth_core::AuthError::internal(error.to_string()));
                }
            };
            if let Err(error) = ctx.email_provider()?.send_message(&message).await {
                super::record_email_send_failure(
                    ctx,
                    Some(user.id()),
                    &body.email,
                    "send_verification_email_endpoint",
                    Some("email_provider"),
                    &error.to_string(),
                    message.metadata.clone(),
                )
                .await;
                return Err(error);
            }
            tracing::info!(
                recipient_email = %body.email,
                user_id = %user.id(),
                flow = "send_verification_email_endpoint",
                provider = "email_provider",
                "verification email sent"
            );
        } else {
            let missing_provider = "No email provider configured";
            super::record_email_send_failure(
                ctx,
                Some(user.id()),
                &body.email,
                "send_verification_email_endpoint",
                None,
                missing_provider,
                None,
            )
            .await;
            tracing::error!(
                recipient_email = %body.email,
                flow = "send_verification_email_endpoint",
                error = missing_provider,
                "Verification email was not sent"
            );
            return Err(AuthError::config(missing_provider));
        }
    }

    Ok(StatusResponse { status: true })
}

pub(super) async fn verify_email_core<DB: DatabaseAdapter>(
    query: &VerifyEmailQuery,
    config: &EmailVerificationConfig,
    ip_address: Option<String>,
    user_agent: Option<String>,
    ctx: &AuthContext<DB>,
) -> AuthResult<VerifyEmailResult<DB::User, DB::Session>> {
    // Find verification token
    let verification = ctx
        .database
        .get_verification_by_value(&query.token)
        .await?
        .ok_or_else(|| AuthError::bad_request("Invalid or expired verification token"))?;

    // Get user by email (stored in identifier field)
    let user = ctx
        .database
        .get_user_by_email(verification.identifier())
        .await?
        .ok_or_else(|| AuthError::not_found("User associated with this token not found"))?;

    // Check if already verified
    if user.email_verified() {
        return Ok(VerifyEmailResult::AlreadyVerified(VerifyEmailResponse {
            user,
            status: true,
        }));
    }

    // Run before_email_verification hook
    if let Some(ref hook) = config.before_email_verification {
        let hook_user = User::from(&user);
        hook(&hook_user).await?;
    }

    // Update user email verification status
    let update_user = UpdateUser {
        email_verified: Some(true),
        ..Default::default()
    };

    let updated_user = ctx.database.update_user(user.id(), update_user).await?;
    if let Err(cache_error) = ctx.cache_session_user(updated_user.id(), &updated_user) {
        tracing::warn!(
            user_id = %updated_user.id(),
            error = %cache_error,
            "failed to refresh session user cache after email verification"
        );
    }

    // Delete the used verification token
    ctx.database.delete_verification(verification.id()).await?;

    // Run after_email_verification hook
    if let Some(ref hook) = config.after_email_verification {
        let hook_user = User::from(&updated_user);
        hook(&hook_user).await?;
    }

    // Optionally create a session when auto_sign_in_after_verification is enabled.
    let session_info = if config.auto_sign_in_after_verification {
        let session = ctx
            .session_manager()
            .create_session(&updated_user, ip_address, user_agent)
            .await?;
        let token = session.token().to_string();
        Some((session, token))
    } else {
        None
    };

    // If callback URL is provided, redirect
    if let Some(ref callback_url) = query.callback_url {
        let redirect_url = format!("{}?verified=true", callback_url);
        return Ok(VerifyEmailResult::Redirect {
            url: redirect_url,
            session_token: session_info.map(|(_, t)| t),
        });
    }

    // Return JSON
    match session_info {
        Some((session, token)) => Ok(VerifyEmailResult::JsonWithSession {
            response: VerifyEmailWithSessionResponse {
                user: updated_user,
                session: session_with_server_version(session),
                status: true,
            },
            session_token: token,
        }),
        None => Ok(VerifyEmailResult::Json(VerifyEmailResponse {
            user: updated_user,
            status: true,
        })),
    }
}
