package handlers

import (
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"net/http"

	"nano-router/auth"
	"nano-router/config"
)

func HandleAuthChallenge(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		fail(w, http.StatusMethodNotAllowed, "method not allowed")
		return
	}
	nonce := auth.Global.CreateChallenge()
	ok(w, map[string]string{"nonce": nonce})
}

func HandleAuthLogin(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		fail(w, http.StatusMethodNotAllowed, "method not allowed")
		return
	}

	var req struct {
		Nonce    string `json:"nonce"`
		Response string `json:"response"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		fail(w, http.StatusBadRequest, "invalid request")
		return
	}

	clientIP := auth.GetClientIP(r)
	sessionID, loginOk := auth.Global.Login(req.Nonce, req.Response, clientIP)
	if !loginOk {
		fail(w, http.StatusUnauthorized, "invalid credentials or expired challenge")
		return
	}

	auth.SetSessionCookie(w, sessionID)
	username, _, isDefault := auth.Global.GetCredentials()
	ok(w, map[string]interface{}{
		"message":          "authenticated",
		"default_password": isDefault,
		"username":         username,
	})
}

func HandleAuthLogout(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		fail(w, http.StatusMethodNotAllowed, "method not allowed")
		return
	}
	token := auth.GetSessionToken(r)
	if token != "" {
		auth.Global.DestroySession(token)
	}
	auth.ClearSessionCookie(w)
	ok(w, map[string]string{"message": "logged out"})
}

func HandleAuthStatus(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		fail(w, http.StatusMethodNotAllowed, "method not allowed")
		return
	}
	username, apiKey, isDefault := auth.Global.GetCredentials()
	ok(w, map[string]interface{}{
		"authenticated":    true,
		"username":         username,
		"default_password": isDefault,
		"has_api_key":      apiKey != "",
	})
}

func HandleAuthProfile(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case http.MethodGet:
		username, apiKey, isDefault := auth.Global.GetCredentials()
		ok(w, map[string]interface{}{
			"username":        username,
			"default_password": isDefault,
			"has_api_key":     apiKey != "",
			"api_key_prefix":  apiKeyPrefix(apiKey),
		})

	case http.MethodPost:
		var req struct {
			Username     string `json:"username"`
			OldPassword  string `json:"old_password"`
			NewPassword  string `json:"new_password"`
			NewPassword2 string `json:"new_password2"`
		}
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			fail(w, http.StatusBadRequest, "invalid request")
			return
		}

		if req.Username == "" && req.NewPassword == "" {
			fail(w, http.StatusBadRequest, "nothing to update")
			return
		}

		usernameChanged := false
		passwordChanged := false

		if req.NewPassword != "" {
			if req.OldPassword == "" {
				fail(w, http.StatusBadRequest, "current password required")
				return
			}
			oldHash := auth.HashPassword(req.OldPassword)
			if !auth.Global.VerifyPassword(oldHash) {
				fail(w, http.StatusUnauthorized, "current password incorrect")
				return
			}
			if req.NewPassword != req.NewPassword2 {
				fail(w, http.StatusBadRequest, "passwords do not match")
				return
			}
			if len(req.NewPassword) < 4 {
				fail(w, http.StatusBadRequest, "password too short (min 4 chars)")
				return
			}

			newHash := auth.HashPassword(req.NewPassword)
			clientIP := auth.GetClientIP(r)
			newSessionID := auth.Global.UpdatePassword(newHash, clientIP)
			auth.SetSessionCookie(w, newSessionID)
			passwordChanged = true

			cfg, _ := config.Load()
			cfg.Auth.PasswordHash = newHash
			if req.Username != "" {
				cfg.Auth.Username = req.Username
				auth.Global.UpdateUsername(req.Username)
				usernameChanged = true
			}
			config.Save(cfg)
		} else if req.Username != "" {
			auth.Global.UpdateUsername(req.Username)
			usernameChanged = true
			cfg, _ := config.Load()
			cfg.Auth.Username = req.Username
			config.Save(cfg)
		}

		username, apiKey, isDefault := auth.Global.GetCredentials()
		result := map[string]interface{}{
			"username":        username,
			"default_password": isDefault,
			"has_api_key":     apiKey != "",
			"api_key_prefix":  apiKeyPrefix(apiKey),
		}
		if passwordChanged {
			result["password_changed"] = true
		}
		if usernameChanged {
			result["username_changed"] = true
		}
		ok(w, result)

	default:
		fail(w, http.StatusMethodNotAllowed, "method not allowed")
	}
}

func HandleAuthAPIKey(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case http.MethodPost:
		key := generateAPIKey()
		auth.Global.SetAPIKey(key)

		cfg, _ := config.Load()
		cfg.Auth.APIKey = key
		config.Save(cfg)

		ok(w, map[string]interface{}{
			"api_key": key,
		})

	case http.MethodDelete:
		auth.Global.SetAPIKey("")

		cfg, _ := config.Load()
		cfg.Auth.APIKey = ""
		config.Save(cfg)

		ok(w, map[string]string{"message": "api key revoked"})

	default:
		fail(w, http.StatusMethodNotAllowed, "method not allowed")
	}
}

func generateAPIKey() string {
	b := make([]byte, 32)
	rand.Read(b)
	return "ar_" + hex.EncodeToString(b)
}

func apiKeyPrefix(key string) string {
	if len(key) > 8 {
		return key[:8] + "..."
	}
	return ""
}