package web

import (
	"crypto/rand"
	"crypto/subtle"
	"encoding/hex"
	"fmt"
	"net/http"
	"strings"
	"time"

	"golang.org/x/crypto/bcrypt"
)

type session struct {
	token     string
	expiresAt time.Time
}

const (
	sessionCookieName = "felhom_session"
	sessionMaxAge     = 24 * time.Hour
)

// RequireAuth returns middleware that checks for valid session or shows login.
func (s *Server) RequireAuth(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Skip auth if no password is configured
		if s.cfg.Web.PasswordHash == "" {
			next.ServeHTTP(w, r)
			return
		}

		if r.URL.Path == "/api/health" {
			next.ServeHTTP(w, r)
			return
		}

		if r.URL.Path == "/login" && r.Method == http.MethodPost {
			s.handleLogin(w, r)
			return
		}
		if r.URL.Path == "/login" {
			s.renderLogin(w, "")
			return
		}
		if r.URL.Path == "/logout" {
			s.handleLogout(w, r)
			return
		}

		cookie, err := r.Cookie(sessionCookieName)
		if err != nil || !s.isValidSession(cookie.Value) {
			if strings.HasPrefix(r.URL.Path, "/api/") {
				w.Header().Set("Content-Type", "application/json")
				w.WriteHeader(http.StatusUnauthorized)
				fmt.Fprint(w, `{"ok":false,"error":"authentication required"}`)
				return
			}
			http.Redirect(w, r, "/login", http.StatusFound)
			return
		}

		next.ServeHTTP(w, r)
	})
}

func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
	_ = r.ParseForm()
	password := r.FormValue("password")

	if password == "" {
		s.renderLogin(w, "Kérjük adja meg a jelszót")
		return
	}

	if err := bcrypt.CompareHashAndPassword([]byte(s.cfg.Web.PasswordHash), []byte(password)); err != nil {
		s.logger.Printf("[WARN] Failed login from %s", r.RemoteAddr)
		s.renderLogin(w, "Hibás jelszó")
		return
	}

	token := s.createSession()
	http.SetCookie(w, &http.Cookie{
		Name:     sessionCookieName,
		Value:    token,
		Path:     "/",
		MaxAge:   int(sessionMaxAge.Seconds()),
		HttpOnly: true,
		SameSite: http.SameSiteStrictMode,
		Secure:   true,
	})

	s.logger.Printf("[INFO] Login from %s", r.RemoteAddr)
	http.Redirect(w, r, "/", http.StatusFound)
}

func (s *Server) handleLogout(w http.ResponseWriter, r *http.Request) {
	if cookie, err := r.Cookie(sessionCookieName); err == nil {
		s.sessionsMu.Lock()
		delete(s.sessions, cookie.Value)
		s.sessionsMu.Unlock()
	}
	http.SetCookie(w, &http.Cookie{Name: sessionCookieName, Value: "", Path: "/", MaxAge: -1})
	http.Redirect(w, r, "/login", http.StatusFound)
}

func (s *Server) createSession() string {
	b := make([]byte, 32)
	_, _ = rand.Read(b)
	token := hex.EncodeToString(b)

	s.sessionsMu.Lock()
	s.sessions[token] = &session{token: token, expiresAt: time.Now().Add(sessionMaxAge)}
	s.sessionsMu.Unlock()

	return token
}

func (s *Server) isValidSession(token string) bool {
	s.sessionsMu.RLock()
	defer s.sessionsMu.RUnlock()

	sess, ok := s.sessions[token]
	if !ok || time.Now().After(sess.expiresAt) {
		return false
	}
	return subtle.ConstantTimeCompare([]byte(sess.token), []byte(token)) == 1
}

func (s *Server) cleanupSessions() {
	for range time.Tick(15 * time.Minute) {
		s.sessionsMu.Lock()
		now := time.Now()
		for t, sess := range s.sessions {
			if now.After(sess.expiresAt) {
				delete(s.sessions, t)
			}
		}
		s.sessionsMu.Unlock()
	}
}

func (s *Server) renderLogin(w http.ResponseWriter, errorMsg string) {
	data := map[string]interface{}{
		"Title":        "Bejelentkezés",
		"CustomerName": s.cfg.Customer.Name,
		"Error":        errorMsg,
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	if err := s.tmpl.ExecuteTemplate(w, "login", data); err != nil {
		s.logger.Printf("[ERROR] Template error (login): %v", err)
		http.Error(w, "Internal error", http.StatusInternalServerError)
	}
}
