package handlers

import (
	"errors"
	"net"
	"net/http"
	"strings"

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

// Auth handles authentication HTTP requests.
type Auth struct {
	svc    *service.AuthService
	logger *logging.Logger
}

// NewAuth creates a new Auth handler.
func NewAuth(svc *service.AuthService, logger *logging.Logger) *Auth {
	return &Auth{
		svc:    svc,
		logger: logger.WithComponent("AuthHandler"),
	}
}

// --- Request / Response types ---

// LoginRequest is the request body for password login.
type LoginRequest struct {
	Email    string `json:"email" validate:"required,email"`
	Password string `json:"password" validate:"required,min=1"`
}

// RegisterRequest is the request body for registration.
type RegisterRequest struct {
	Email    string `json:"email" validate:"required,email"`
	Password string `json:"password" validate:"required,min=8"`
	Name     string `json:"name"`
}

// LoginResponse is the response for successful login or registration.
type LoginResponse struct {
	Token string       `json:"token"`
	User  UserResponse `json:"user"`
}

// UserResponse is the user data returned in auth responses.
type UserResponse struct {
	ID            string   `json:"id"`
	Email         string   `json:"email"`
	Name          string   `json:"name,omitempty"`
	AvatarURL     string   `json:"avatarUrl,omitempty"`
	EmailVerified bool     `json:"emailVerified"`
	Roles         []string `json:"roles,omitempty"`
}

// UpdateProfileRequest is the request body for updating the user profile.
type UpdateProfileRequest struct {
	Name      string `json:"name"`
	AvatarURL string `json:"avatarUrl"`
}

// ChangePasswordRequest is the request body for changing password.
type ChangePasswordRequest struct {
	CurrentPassword string `json:"currentPassword" validate:"required"`
	NewPassword     string `json:"newPassword" validate:"required,min=8"`
}

// RefreshRequest is the request body for refreshing an access token.
type RefreshRequest struct {
	Token string `json:"token" validate:"required"`
}

// toUserResponse converts a domain.User to UserResponse.
func toUserResponse(u *domain.User) UserResponse {
	return UserResponse{
		ID:            string(u.ID),
		Email:         u.Email,
		Name:          u.Name,
		AvatarURL:     u.AvatarURL,
		EmailVerified: u.EmailVerified,
		Roles:         u.Roles,
	}
}

// toLoginResponse creates a LoginResponse from service output.
func toLoginResponse(out *service.LoginOutput) LoginResponse {
	return LoginResponse{
		Token: out.Token,
		User:  toUserResponse(out.User),
	}
}

// --- Handlers ---

// Login authenticates a user with email and password.
//
// POST /api/{service}/auth/login
func (h *Auth) Login(w http.ResponseWriter, r *http.Request) error {
	var req LoginRequest
	if err := app.BindAndValidate(r, &req); err != nil {
		return err
	}

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

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

// Register creates a new user account.
//
// POST /api/{service}/auth/register
func (h *Auth) Register(w http.ResponseWriter, r *http.Request) error {
	var req RegisterRequest
	if err := app.BindAndValidate(r, &req); err != nil {
		return err
	}

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

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

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

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

	httpresponse.OK(w, r, toUserResponse(freshUser))
	return nil
}

// UpdateMe updates the current user's profile.
//
// PUT /api/{service}/auth/me
func (h *Auth) UpdateMe(w http.ResponseWriter, r *http.Request) error {
	user, err := auth.GetUserOrError(r.Context())
	if err != nil {
		return httperror.Unauthorized("not authenticated")
	}

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

	updated, err := h.svc.UpdateProfile(r.Context(), user.ID, req.Name, req.AvatarURL)
	if err != nil {
		return mapAuthError(err)
	}

	httpresponse.OK(w, r, toUserResponse(updated))
	return nil
}

// ChangePassword changes the current user's password.
//
// POST /api/{service}/auth/change-password
func (h *Auth) ChangePassword(w http.ResponseWriter, r *http.Request) error {
	user, err := auth.GetUserOrError(r.Context())
	if err != nil {
		return httperror.Unauthorized("not authenticated")
	}

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

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

	httpresponse.NoContent(w)
	return nil
}

// Logout revokes the current session.
//
// POST /api/{service}/auth/logout
func (h *Auth) Logout(w http.ResponseWriter, r *http.Request) error {
	user := auth.GetUser(r.Context())
	if user == nil {
		httpresponse.NoContent(w)
		return nil
	}

	sessionID := ""
	if user.Metadata != nil {
		if sid, ok := user.Metadata["sid"].(string); ok {
			sessionID = sid
		}
	}

	if err := h.svc.Logout(r.Context(), sessionID); err != nil {
		h.logger.Warn("logout session revoke failed", "error", err)
	}

	httpresponse.NoContent(w)
	return nil
}

// RefreshToken issues a new access token for an active session.
//
// POST /api/{service}/auth/refresh
func (h *Auth) RefreshToken(w http.ResponseWriter, r *http.Request) error {
	// The caller sends their current (possibly near-expiry) token.
	// We parse it to get user ID and session ID, then issue a new one.
	user := auth.GetUser(r.Context())
	if user == nil {
		return httperror.Unauthorized("not authenticated")
	}

	sessionID := ""
	if user.Metadata != nil {
		if sid, ok := user.Metadata["sid"].(string); ok {
			sessionID = sid
		}
	}
	if sessionID == "" {
		return httperror.Unauthorized("no session")
	}

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

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

// --- Helpers ---

// mapAuthError translates domain errors to HTTP errors.
func mapAuthError(err error) error {
	switch {
	case errors.Is(err, domain.ErrInvalidCredentials):
		return httperror.Unauthorized("invalid email or password")
	case errors.Is(err, domain.ErrUserNotFound):
		return httperror.Unauthorized("invalid email or password")
	case errors.Is(err, domain.ErrUserSuspended):
		return httperror.Forbidden("account is suspended")
	case errors.Is(err, domain.ErrDuplicateEmail):
		return httperror.Conflict("email already registered")
	case errors.Is(err, domain.ErrWeakPassword):
		return httperror.BadRequest(err.Error())
	case errors.Is(err, domain.ErrRegistrationDisabled):
		return httperror.Forbidden("registration is currently disabled")
	case errors.Is(err, domain.ErrNameTooLong), errors.Is(err, domain.ErrEmailTooLong):
		return httperror.BadRequest(err.Error())
	case errors.Is(err, domain.ErrInvalidAvatarURL):
		return httperror.BadRequest("avatar URL must use http or https")
	case errors.Is(err, domain.ErrSessionNotFound):
		return httperror.NotFound("session not found")
	case errors.Is(err, domain.ErrSessionRevoked):
		return httperror.Unauthorized("session has been revoked")
	case errors.Is(err, domain.ErrInvalidAuthCode):
		return httperror.Unauthorized("invalid or expired code")
	default:
		return err
	}
}

// clientIP extracts the client IP from the request.
// It prefers RemoteAddr (set by the Go HTTP server from the TCP connection) and
// only uses X-Forwarded-For/X-Real-Ip when the direct connection is from a
// private/loopback address, indicating a trusted reverse proxy.
func clientIP(r *http.Request) string {
	host, _, err := net.SplitHostPort(r.RemoteAddr)
	if err != nil {
		host = r.RemoteAddr
	}

	// Only trust proxy headers when the connection is from a private network.
	if isPrivateIP(host) {
		if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
			parts := strings.SplitN(xff, ",", 2)
			ip := strings.TrimSpace(parts[0])
			if ip != "" {
				return ip
			}
		}
		if xri := r.Header.Get("X-Real-Ip"); xri != "" {
			return xri
		}
	}

	return host
}

// isPrivateIP returns true if the address is loopback or RFC 1918 private.
func isPrivateIP(addr string) bool {
	ip := net.ParseIP(addr)
	if ip == nil {
		return false
	}
	return ip.IsLoopback() || ip.IsPrivate()
}

// sessionID extracts the session ID from the authenticated user's metadata.
func sessionID(user *auth.User) string {
	if user == nil || user.Metadata == nil {
		return ""
	}
	sid, _ := user.Metadata["sid"].(string)
	return sid
}
