package handlers

import (
	"errors"
	"net/http"

	"github.com/go-chi/chi/v5"

	"{{GO_MODULE}}/pkg/app"
	"{{GO_MODULE}}/pkg/auth"
	"{{GO_MODULE}}/pkg/httperror"
	"{{GO_MODULE}}/pkg/httpresponse"
	"{{GO_MODULE}}/services/{{COMPONENT_NAME}}/internal/domain"
)

// --- Request types for auth flows ---

// EmailRequest is used by OTP send, magic link, and forgot password.
type EmailRequest struct {
	Email string `json:"email" validate:"required,email"`
}

// OTPVerifyRequest verifies a one-time password.
type OTPVerifyRequest struct {
	Email string `json:"email" validate:"required,email"`
	Code  string `json:"code" validate:"required,len=6"`
}

// MagicLinkVerifyRequest verifies a magic link token.
type MagicLinkVerifyRequest struct {
	Email string `json:"email" validate:"required,email"`
	Token string `json:"token" validate:"required"`
}

// ResetPasswordRequest sets a new password using a reset token.
type ResetPasswordRequest struct {
	Email       string `json:"email" validate:"required,email"`
	Token       string `json:"token" validate:"required"`
	NewPassword string `json:"newPassword" validate:"required,min=8"`
}

// VerifyEmailRequest verifies an email with a code.
type VerifyEmailRequest struct {
	Code string `json:"code" validate:"required,len=6"`
}

// SessionResponse is a single session in the list.
type SessionResponse struct {
	ID           string `json:"id"`
	IPAddress    string `json:"ipAddress"`
	DeviceLabel  string `json:"deviceLabel"`
	LastActiveAt string `json:"lastActiveAt"`
	CreatedAt    string `json:"createdAt"`
	IsCurrent    bool   `json:"isCurrent"`
}

// --- OTP handlers ---

// SendOTP sends a one-time password to the user's email.
//
// POST /api/{service}/auth/otp/send
func (h *Auth) SendOTP(w http.ResponseWriter, r *http.Request) error {
	var req EmailRequest
	if err := app.BindAndValidate(r, &req); err != nil {
		return err
	}

	if err := h.svc.SendOTP(r.Context(), req.Email, clientIP(r)); err != nil {
		return mapAuthError(err)
	}

	httpresponse.OK(w, r, map[string]string{"message": "If an account exists, a code has been sent"})
	return nil
}

// VerifyOTP verifies a one-time password and returns a login token.
//
// POST /api/{service}/auth/otp/verify
func (h *Auth) VerifyOTP(w http.ResponseWriter, r *http.Request) error {
	var req OTPVerifyRequest
	if err := app.BindAndValidate(r, &req); err != nil {
		return err
	}

	output, err := h.svc.VerifyOTP(r.Context(), req.Email, req.Code, clientIP(r), r.UserAgent())
	if err != nil {
		return mapAuthError(err)
	}

	httpresponse.OK(w, r, toLoginResponse(output))
	return nil
}

// --- Magic Link handlers ---

// SendMagicLink sends a magic link to the user's email.
//
// POST /api/{service}/auth/magic-link
func (h *Auth) SendMagicLink(w http.ResponseWriter, r *http.Request) error {
	var req EmailRequest
	if err := app.BindAndValidate(r, &req); err != nil {
		return err
	}

	if err := h.svc.SendMagicLink(r.Context(), req.Email, clientIP(r)); err != nil {
		return mapAuthError(err)
	}

	httpresponse.OK(w, r, map[string]string{"message": "If an account exists, a link has been sent"})
	return nil
}

// VerifyMagicLink verifies a magic link token and returns a login token.
//
// POST /api/{service}/auth/magic-link/verify
func (h *Auth) VerifyMagicLink(w http.ResponseWriter, r *http.Request) error {
	var req MagicLinkVerifyRequest
	if err := app.BindAndValidate(r, &req); err != nil {
		return err
	}

	output, err := h.svc.VerifyMagicLink(r.Context(), req.Email, req.Token, clientIP(r), r.UserAgent())
	if err != nil {
		return mapAuthError(err)
	}

	httpresponse.OK(w, r, toLoginResponse(output))
	return nil
}

// --- Forgot / Reset Password handlers ---

// ForgotPassword sends a password reset token.
//
// POST /api/{service}/auth/forgot-password
func (h *Auth) ForgotPassword(w http.ResponseWriter, r *http.Request) error {
	var req EmailRequest
	if err := app.BindAndValidate(r, &req); err != nil {
		return err
	}

	if err := h.svc.ForgotPassword(r.Context(), req.Email, clientIP(r)); err != nil {
		return mapAuthError(err)
	}

	httpresponse.OK(w, r, map[string]string{"message": "If an account exists, a reset link has been sent"})
	return nil
}

// ResetPassword sets a new password using a reset token.
//
// POST /api/{service}/auth/reset-password
func (h *Auth) ResetPassword(w http.ResponseWriter, r *http.Request) error {
	var req ResetPasswordRequest
	if err := app.BindAndValidate(r, &req); err != nil {
		return err
	}

	if err := h.svc.ResetPassword(r.Context(), req.Email, req.Token, req.NewPassword); err != nil {
		return mapAuthError(err)
	}

	httpresponse.OK(w, r, map[string]string{"message": "Password has been reset. Please sign in."})
	return nil
}

// --- Email Verification handlers ---

// SendVerifyEmail sends a verification code to the current user's email.
//
// POST /api/{service}/auth/verify-email/send
func (h *Auth) SendVerifyEmail(w http.ResponseWriter, r *http.Request) error {
	user, err := auth.GetUserOrError(r.Context())
	if err != nil {
		return httperror.Unauthorized("not authenticated")
	}

	if err := h.svc.SendVerifyEmail(r.Context(), user.ID); err != nil {
		return mapAuthError(err)
	}

	httpresponse.OK(w, r, map[string]string{"message": "Verification code sent"})
	return nil
}

// VerifyEmail verifies the current user's email with a code.
//
// POST /api/{service}/auth/verify-email
func (h *Auth) VerifyEmail(w http.ResponseWriter, r *http.Request) error {
	user, err := auth.GetUserOrError(r.Context())
	if err != nil {
		return httperror.Unauthorized("not authenticated")
	}

	var req VerifyEmailRequest
	if err := app.BindAndValidate(r, &req); err != nil {
		return err
	}

	if err := h.svc.VerifyEmail(r.Context(), user.ID, req.Code); err != nil {
		return mapAuthError(err)
	}

	httpresponse.OK(w, r, map[string]string{"message": "Email verified"})
	return nil
}

// --- Session Management handlers ---

// ListSessions returns all active sessions for the current user.
//
// GET /api/{service}/auth/sessions
func (h *Auth) ListSessions(w http.ResponseWriter, r *http.Request) error {
	user, err := auth.GetUserOrError(r.Context())
	if err != nil {
		return httperror.Unauthorized("not authenticated")
	}

	currentSID := sessionID(user)

	sessions, err := h.svc.ListSessions(r.Context(), user.ID)
	if err != nil {
		return err
	}

	result := make([]SessionResponse, 0, len(sessions))
	for _, s := range sessions {
		result = append(result, SessionResponse{
			ID:           string(s.ID),
			IPAddress:    s.IPAddress,
			DeviceLabel:  s.DeviceLabel,
			LastActiveAt: s.LastActiveAt.Format("2006-01-02T15:04:05Z07:00"),
			CreatedAt:    s.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
			IsCurrent:    string(s.ID) == currentSID,
		})
	}

	httpresponse.OK(w, r, result)
	return nil
}

// RevokeSession revokes a specific session.
//
// DELETE /api/{service}/auth/sessions/{id}
func (h *Auth) RevokeSession(w http.ResponseWriter, r *http.Request) error {
	user, err := auth.GetUserOrError(r.Context())
	if err != nil {
		return httperror.Unauthorized("not authenticated")
	}

	sid := chi.URLParam(r, "id")
	if sid == "" {
		return httperror.BadRequest("session id required")
	}

	if err := h.svc.RevokeSession(r.Context(), user.ID, sid); err != nil {
		if errors.Is(err, domain.ErrSessionNotFound) {
			return httperror.NotFound("session not found")
		}
		return err
	}

	httpresponse.NoContent(w)
	return nil
}

// RevokeAllSessions revokes all sessions except the current one.
//
// DELETE /api/{service}/auth/sessions
func (h *Auth) RevokeAllSessions(w http.ResponseWriter, r *http.Request) error {
	user, err := auth.GetUserOrError(r.Context())
	if err != nil {
		return httperror.Unauthorized("not authenticated")
	}

	currentSID := sessionID(user)
	var except *string
	if currentSID != "" {
		except = &currentSID
	}

	if err := h.svc.LogoutAll(r.Context(), user.ID, except); err != nil {
		return err
	}

	httpresponse.NoContent(w)
	return nil
}
