package auth

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"time"

	"gitea.local/admin/hspguard/internal/config"
	imiddleware "gitea.local/admin/hspguard/internal/middleware"
	"gitea.local/admin/hspguard/internal/repository"
	"gitea.local/admin/hspguard/internal/types"
	"gitea.local/admin/hspguard/internal/util"
	"gitea.local/admin/hspguard/internal/web"
	"github.com/go-chi/chi/v5"
	"github.com/golang-jwt/jwt/v5"
	"github.com/google/uuid"
)

type AuthHandler struct {
	repo *repository.Queries
	cfg  *config.AppConfig
}

func (h *AuthHandler) signTokens(user *repository.User) (string, string, error) {
	accessClaims := types.UserClaims{
		UserEmail: user.Email,
		IsAdmin:   user.IsAdmin,
		RegisteredClaims: jwt.RegisteredClaims{
			Issuer:    h.cfg.Jwt.Issuer,
			Subject:   user.ID.String(),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)),
		},
	}

	accessToken, err := util.SignJwtToken(accessClaims, h.cfg.Jwt.PrivateKey)
	if err != nil {
		return "", "", err
	}

	refreshClaims := types.UserClaims{
		UserEmail: user.Email,
		IsAdmin:   user.IsAdmin,
		RegisteredClaims: jwt.RegisteredClaims{
			Issuer:    h.cfg.Jwt.Issuer,
			Subject:   user.ID.String(),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(30 * 24 * time.Hour)),
		},
	}

	refreshToken, err := util.SignJwtToken(refreshClaims, h.cfg.Jwt.PrivateKey)
	if err != nil {
		return "", "", err
	}

	return accessToken, refreshToken, nil
}

func NewAuthHandler(repo *repository.Queries, cfg *config.AppConfig) *AuthHandler {
	return &AuthHandler{
		repo,
		cfg,
	}
}

func (h *AuthHandler) RegisterRoutes(api chi.Router) {
	api.Route("/auth", func(r chi.Router) {
		r.Group(func(protected chi.Router) {
			authMiddleware := imiddleware.NewAuthMiddleware(h.cfg)
			protected.Use(authMiddleware.Runner)

			protected.Get("/profile", h.getProfile)
		})

		r.Post("/login", h.login)
		r.Post("/refresh", h.refreshToken)
	})
}

func (h *AuthHandler) refreshToken(w http.ResponseWriter, r *http.Request) {
	authHeader := r.Header.Get("Authorization")
	if authHeader == "" {
		web.Error(w, "unauthorized", http.StatusUnauthorized)
		return
	}

	parts := strings.Split(authHeader, "Bearer ")
	if len(parts) != 2 {
		web.Error(w, "invalid auth header format", http.StatusUnauthorized)
		return
	}

	tokenStr := parts[1]
	token, userClaims, err := util.VerifyToken(tokenStr, h.cfg.Jwt.PublicKey)
	if err != nil || !token.Valid {
		http.Error(w, fmt.Sprintf("invalid token: %v", err), http.StatusUnauthorized)
		return
	}

	expire, err := userClaims.GetExpirationTime()
	if err != nil {
		web.Error(w, "failed to retrieve enough info from the token", http.StatusInternalServerError)
		return
	}

	if time.Now().After(expire.Time) {
		web.Error(w, "token is expired", http.StatusUnauthorized)
		return
	}

	userId, err := uuid.Parse(userClaims.Subject)
	if err != nil {
		web.Error(w, "failed to parsej user id from token", http.StatusInternalServerError)
		return
	}

	user, err := h.repo.FindUserId(r.Context(), userId)
	if err != nil {
		web.Error(w, "user with provided email does not exists", http.StatusBadRequest)
		return
	}

	access, refresh, err := h.signTokens(&user)
	if err != nil {
		web.Error(w, "failed to generate tokens", http.StatusInternalServerError)
		return
	}

	type Response struct {
		AccessToken  string `json:"access"`
		RefreshToken string `json:"refresh"`
	}

	encoder := json.NewEncoder(w)

	if err := encoder.Encode(Response{
		AccessToken:  access,
		RefreshToken: refresh,
	}); err != nil {
		web.Error(w, "failed to encode response", http.StatusInternalServerError)
	}
}

func (h *AuthHandler) getProfile(w http.ResponseWriter, r *http.Request) {
	userId, ok := util.GetRequestUserId(r.Context())
	if !ok {
		web.Error(w, "failed to get user id from auth session", http.StatusInternalServerError)
		return
	}

	user, err := h.repo.FindUserId(r.Context(), uuid.MustParse(userId))
	if err != nil {
		web.Error(w, "user with provided id does not exist", http.StatusUnauthorized)
		return
	}

	w.Header().Set("Content-Type", "application/json")

	if err := json.NewEncoder(w).Encode(map[string]any{
		"id":              user.ID.String(),
		"full_name":       user.FullName,
		"email":           user.Email,
		"phone_number":    user.PhoneNumber,
		"isAdmin":         user.IsAdmin,
		"last_login":      user.LastLogin,
		"profile_picture": user.ProfilePicture.String,
		"updated_at":      user.UpdatedAt,
		"created_at":      user.CreatedAt,
	}); err != nil {
		web.Error(w, "failed to encode user profile", http.StatusInternalServerError)
	}
}

type LoginParams struct {
	Email    string `json:"email"`
	Password string `json:"password"`
}

func (h *AuthHandler) login(w http.ResponseWriter, r *http.Request) {
	var params LoginParams

	decoder := json.NewDecoder(r.Body)
	if err := decoder.Decode(&params); err != nil {
		web.Error(w, "failed to parse request body", http.StatusBadRequest)
		return
	}

	if params.Email == "" || params.Password == "" {
		web.Error(w, "missing required fields", http.StatusBadRequest)
		return
	}

	user, err := h.repo.FindUserEmail(r.Context(), params.Email)
	if err != nil {
		web.Error(w, "user with provided email does not exists", http.StatusBadRequest)
		return
	}

	if !util.VerifyPassword(params.Password, user.PasswordHash) {
		web.Error(w, "username or/and password are incorrect", http.StatusBadRequest)
		return
	}

	access, refresh, err := h.signTokens(&user)
	if err != nil {
		web.Error(w, "failed to generate tokens", http.StatusInternalServerError)
		return
	}

	encoder := json.NewEncoder(w)

	type Response struct {
		AccessToken  string `json:"access"`
		RefreshToken string `json:"refresh"`
		// fields required for UI in account selector, e.g. email, full name and avatar
		FullName       string `json:"full_name"`
		Email          string `json:"email"`
		Id             string `json:"id"`
		ProfilePicture string `json:"profile_picture"`
		// Avatar
	}

	w.Header().Set("Content-Type", "application/json")

	if err := encoder.Encode(Response{
		AccessToken:    access,
		RefreshToken:   refresh,
		FullName:       user.FullName,
		Email:          user.Email,
		Id:             user.ID.String(),
		ProfilePicture: user.ProfilePicture.String,
		// Avatar
	}); err != nil {
		web.Error(w, "failed to encode response", http.StatusInternalServerError)
	}
}
