package http

import (
	"api/db"
	"api/structs"
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"strconv"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/cors"
	"github.com/go-chi/jwtauth/v5"
	"golang.org/x/crypto/bcrypt"
)

// var jwtSecret = "password1234"
const ListenPort = ":8081"

type HttpServer struct {
	DBServer  *db.DBServer
	Router    *chi.Mux
	TokenAuth *jwtauth.JWTAuth
}

func NewHttpServer(db *db.DBServer) *HttpServer {
	r := chi.NewRouter()
	r.Use(cors.Handler(cors.Options{
		AllowedOrigins:   []string{"http://*", "http://localhost:5173", "http://localhost:8080", "http://localhost:8081"},
		AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
		AllowedHeaders:   []string{"Accept", "Authorization", "Content-Type"},
		AllowCredentials: false,
		MaxAge:           300,
	}))
	secret := os.Getenv("JWT_SECRET")
	//secret := jwtSecret
	tokenAuth := jwtauth.New("HS256", []byte(secret), nil)
	return &HttpServer{DBServer: db, Router: r, TokenAuth: tokenAuth}
}
func (h *HttpServer) Http() {
	h.Router.Group(func(r chi.Router) {
		r.Post("/signup", h.createUser) // signup
		r.Post("/login", h.loginUser)   // login → returns JWT
		r.Get("/info", h.info)          //debug info
		r.Post("/refresh", h.refreshToken)
	})
	// Protected routes (JWT required)
	h.Router.Group(func(r chi.Router) {
		// 1. Extract & verify the token from the request
		r.Use(jwtauth.Verifier(h.TokenAuth))
		// 2. Reject requests with invalid/missing tokens (401)
		r.Use(jwtauth.Authenticator(h.TokenAuth))

		r.Route("/users", func(r chi.Router) {
			r.Get("/", h.listUsers)

			r.Route("/{userID}", func(r chi.Router) {
				r.Get("/", h.getUser)
				r.Delete("/", h.deleteUser)

				r.Route("/vaults", func(r chi.Router) {
					r.Get("/", h.listVaults)
					r.Post("/", h.createVault)

					r.Route("/{vaultID}", func(r chi.Router) {
						r.Get("/", h.getVault)
						r.Delete("/", h.deleteVault)

						r.Route("/passwords", func(r chi.Router) {
							r.Get("/", h.listPasswords)
							r.Post("/", h.createPassword)

							r.Route("/{passwordID}", func(r chi.Router) {
								r.Get("/", h.getPassword)
								r.Put("/", h.updatePassword)
								r.Delete("/", h.deletePassword)
							})
						})
					})
				})
			})
		})
	})
	//http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
	//	fmt.Fprintf(w, "Hello, you've requested: %s\n", r.URL.Path)
	//})

	err := http.ListenAndServe(ListenPort, h.Router)
	if err != nil {
		panic(err)
	}
}

func (h *HttpServer) info(w http.ResponseWriter, r *http.Request) {
	hostname, _ := os.Hostname()
	w.Write([]byte(hostname))
}

func (h *HttpServer) refreshToken(w http.ResponseWriter, r *http.Request) {
	var input struct {
		RefreshToken string `json:"refresh_token"`
	}
	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
		http.Error(w, "invalid request body", http.StatusBadRequest)
		return
	}

	// 1. Hash the incoming refresh token
	hashedInputToken := hashToken(input.RefreshToken)

	// 2. Validate it against the database (Ensure you added ValidateRefreshToken to db.go!)
	userID, err := h.DBServer.ValidateRefreshToken(hashedInputToken)
	if err != nil {
		http.Error(w, "invalid or expired refresh token", http.StatusUnauthorized)
		return
	}

	// Get the user to populate the new JWT claims
	user, err := h.DBServer.GetUser(strconv.FormatInt(userID, 10))
	if err != nil {
		http.Error(w, "user not found", http.StatusInternalServerError)
		return
	}

	// 3. Refresh Token Rotation: Delete the old token and generate a new one
	_ = h.DBServer.DeleteRefreshToken(hashedInputToken)

	newRefreshToken, err := generateRefreshToken()
	if err != nil {
		http.Error(w, "error generating token", http.StatusInternalServerError)
		return
	}
	newHashedToken := hashToken(newRefreshToken)
	expiresAt := time.Now().Add(7 * 24 * time.Hour)

	err = h.DBServer.StoreRefreshToken(newHashedToken, userID, expiresAt)
	if err != nil {
		http.Error(w, "error saving session", http.StatusInternalServerError)
		return
	}

	// 4. Issue a new Access Token (15m)
	_, newAccessToken, err := h.TokenAuth.Encode(map[string]interface{}{
		"sub":      strconv.FormatInt(userID, 10),
		"username": user.Username,
		"exp":      time.Now().Add(15 * time.Minute).Unix(),
	})
	if err != nil {
		http.Error(w, "error generating access token", http.StatusInternalServerError)
		return
	}

	// Return the new tokens
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]string{
		"access_token":  newAccessToken,
		"refresh_token": newRefreshToken,
	})
}

func generateRefreshToken() (string, error) {
	bytes := make([]byte, 32)
	if _, err := rand.Read(bytes); err != nil {
		return "", err
	}
	return hex.EncodeToString(bytes), nil
}

func hashToken(token string) string {
	hash := sha256.Sum256([]byte(token))
	return hex.EncodeToString(hash[:])
}

// ===== Users =====
// loginUser validates credentials and returns a JWT
func (h *HttpServer) loginUser(w http.ResponseWriter, r *http.Request) {
	var input struct {
		Username string `json:"username"`
		Password string `json:"password"`
	}
	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
		http.Error(w, "invalid request body", http.StatusBadRequest)
		return
	}

	user, err := h.DBServer.GetUserByUsername(input.Username)
	if err != nil {
		http.Error(w, "invalid credentials", http.StatusUnauthorized)
		return
	}
	e := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(input.Password))
	if e != nil {
		http.Error(w, "invalid credentials", http.StatusUnauthorized)
		return
	}

	// 1. Create SHORT-LIVED Access Token (e.g., 15 minutes)
	_, accessToken, err := h.TokenAuth.Encode(map[string]interface{}{
		"sub":      strconv.FormatInt(user.ID, 10),
		"username": user.Username,
		"exp":      time.Now().Add(15 * time.Minute).Unix(), // Changed from 24h
	})
	if err != nil { /* handle err */
	}

	// 2. Create LONG-LIVED Refresh Token (e.g., 7 days)
	refreshToken, err := generateRefreshToken()
	if err != nil { /* handle err */
	}
	// 2. Hash it to store in the DB
	hashedToken := hashToken(refreshToken)
	expiresAt := time.Now().Add(7 * 24 * time.Hour) // 7 days

	// 3. Store it using the new DB method
	err = h.DBServer.StoreRefreshToken(hashedToken, user.ID, expiresAt)
	if err != nil {
		http.Error(w, "error saving session", http.StatusInternalServerError)
		return
	}

	// 4. Return both (or set refresh_token as an HttpOnly cookie for web frontends)
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]string{
		"access_token":  accessToken,
		"refresh_token": refreshToken,
	})
}

func (h *HttpServer) listUsers(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	users, err := h.DBServer.ListUsers()
	if err != nil {
		http.Error(w, fmt.Sprintf("error listing users: %s", err.Error()), http.StatusInternalServerError)
		return
	}
	json.NewEncoder(w).Encode(users)
}

func (h *HttpServer) createUser(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	var u structs.User
	if err := json.NewDecoder(r.Body).Decode(&u); err != nil {
		http.Error(w, "invalid request body", http.StatusBadRequest)
		return
	}
	created, err := h.DBServer.CreateUser(u)
	if err != nil {
		http.Error(w, fmt.Sprintf("error creating user: %s", err.Error()), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(created)
}

func (h *HttpServer) getUser(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	userId := chi.URLParam(r, "userID")
	u, err := h.DBServer.GetUser(userId)
	if err != nil {
		http.Error(w, fmt.Sprintf("error getting user: %s", err.Error()), http.StatusInternalServerError)
		return
	}
	json.NewEncoder(w).Encode(u)
}

func (h *HttpServer) deleteUser(w http.ResponseWriter, r *http.Request) {
	userId := chi.URLParam(r, "userID")
	err := h.DBServer.DeleteUser(userId)
	if err != nil {
		http.Error(w, fmt.Sprintf("error deleting user: %s", err.Error()), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

// ===== Vaults =====

func (h *HttpServer) listVaults(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	userId := chi.URLParam(r, "userID")
	vaults, err := h.DBServer.ListVaults(userId)
	if err != nil {
		http.Error(w, fmt.Sprintf("error listing vaults: %s", err.Error()), http.StatusInternalServerError)
		return
	}
	json.NewEncoder(w).Encode(vaults)
}

func (h *HttpServer) createVault(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	userId := chi.URLParam(r, "userID")
	userIdInt, err := strconv.ParseInt(userId, 10, 64)
	if err != nil {
		http.Error(w, "invalid user ID", http.StatusBadRequest)
		return
	}
	var v structs.Vault
	if err := json.NewDecoder(r.Body).Decode(&v); err != nil {
		http.Error(w, "invalid request body", http.StatusBadRequest)
		return
	}
	v.UserID = userIdInt
	created, err := h.DBServer.CreateVault(v)
	if err != nil {
		http.Error(w, fmt.Sprintf("error creating vault: %s", err.Error()), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(created)
}

func (h *HttpServer) getVault(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	vaultId := chi.URLParam(r, "vaultID")
	v, err := h.DBServer.GetVault(vaultId)
	if err != nil {
		http.Error(w, fmt.Sprintf("error getting vault: %s", err.Error()), http.StatusInternalServerError)
		return
	}
	json.NewEncoder(w).Encode(v)
}

func (h *HttpServer) deleteVault(w http.ResponseWriter, r *http.Request) {
	vaultId := chi.URLParam(r, "vaultID")
	err := h.DBServer.DeleteVault(vaultId)
	if err != nil {
		http.Error(w, fmt.Sprintf("error deleting vault: %s", err.Error()), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

// ===== Passwords =====

func (h *HttpServer) listPasswords(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	vaultId := chi.URLParam(r, "vaultID")
	passwords, err := h.DBServer.ListPasswords(vaultId)
	if err != nil {
		http.Error(w, fmt.Sprintf("error listing passwords: %s", err.Error()), http.StatusInternalServerError)
		return
	}
	json.NewEncoder(w).Encode(passwords)
}

func (h *HttpServer) createPassword(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	vaultId := chi.URLParam(r, "vaultID")
	vaultIdInt, err := strconv.ParseInt(vaultId, 10, 64)
	if err != nil {
		http.Error(w, "invalid vault ID", http.StatusBadRequest)
		return
	}
	var p structs.PasswordEntry
	if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
		http.Error(w, "invalid request body", http.StatusBadRequest)
		return
	}
	p.VaultID = vaultIdInt
	created, err := h.DBServer.CreatePassword(p)
	if err != nil {
		http.Error(w, fmt.Sprintf("error creating password: %s", err.Error()), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(created)
}

func (h *HttpServer) getPassword(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	passwordId := chi.URLParam(r, "passwordID")
	p, err := h.DBServer.GetPassword(passwordId)
	if err != nil {
		http.Error(w, fmt.Sprintf("error getting password: %s", err.Error()), http.StatusInternalServerError)
		return
	}
	json.NewEncoder(w).Encode(p)
}

func (h *HttpServer) updatePassword(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	passwordId := chi.URLParam(r, "passwordID")
	passwordIdInt, err := strconv.ParseInt(passwordId, 10, 64)
	if err != nil {
		http.Error(w, "invalid password ID", http.StatusBadRequest)
		return
	}
	var p structs.PasswordEntry
	if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
		http.Error(w, "invalid request body", http.StatusBadRequest)
		return
	}
	p.ID = passwordIdInt
	updated, err := h.DBServer.UpdatePassword(p)
	if err != nil {
		http.Error(w, fmt.Sprintf("error updating password: %s", err.Error()), http.StatusInternalServerError)
		return
	}
	json.NewEncoder(w).Encode(updated)
}

func (h *HttpServer) deletePassword(w http.ResponseWriter, r *http.Request) {
	passwordId := chi.URLParam(r, "passwordID")
	err := h.DBServer.DeletePassword(passwordId)
	if err != nil {
		http.Error(w, fmt.Sprintf("error deleting password: %s", err.Error()), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}
