package web

import (
	"errors"
	"fmt"
	"net"
	"net/http"
	"sync"
	"time"

	"golang.org/x/time/rate"
	"party.at/party/internal/data"
	"party.at/party/internal/validator"
)

const cookieName = "session"

func (web *Web) AuthenticateCookie(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		cookie, err := r.Cookie(cookieName)
		if err != nil {
			r = setUser(r, data.AnonymousUser)
			next.ServeHTTP(w, r)
			return
		}

		v := validator.New()
		if data.ValidateTokenPlaintext(v, cookie.Value); !v.Valid() {
			clearCookie(w)
			r = setUser(r, data.AnonymousUser)
			next.ServeHTTP(w, r)
			return
		}

		userIdentity, err := web.App.Models.UserIdentities.GetForToken(data.ScopeAuthentication, cookie.Value)
		if err != nil {
			if !errors.Is(err, data.ErrRecordNotFound) {
				web.App.LogError(r, err)
				http.Error(w, "Internal Server Error", http.StatusInternalServerError)
				return
			}
			clearCookie(w)
			r = setUser(r, data.AnonymousUser)
			next.ServeHTTP(w, r)
			return
		}

		user, err := web.App.Models.Users.Get(userIdentity.UserID)
		if err != nil {
			if !errors.Is(err, data.ErrRecordNotFound) {
				web.App.LogError(r, err)
				http.Error(w, "Internal Server Error", http.StatusInternalServerError)
				return
			}
			clearCookie(w)
			r = setUser(r, data.AnonymousUser)
			next.ServeHTTP(w, r)
			return
		}

		r = setUser(r, user)
		permissions, _ := web.App.Models.Permissions.GetAllForUser(user.ID)
		r = setPermissions(r, permissions)
		next.ServeHTTP(w, r)
	})
}

func setCookie(w http.ResponseWriter, token string, expiry time.Time) {
	http.SetCookie(w, &http.Cookie{
		Name:     cookieName,
		Value:    token,
		Expires:  expiry,
		Path:     "/",
		HttpOnly: true,
		SameSite: http.SameSiteLaxMode,
	})
}

func clearCookie(w http.ResponseWriter) {
	http.SetCookie(w, &http.Cookie{
		Name:     cookieName,
		Value:    "",
		Expires:  time.Unix(0, 0),
		Path:     "/",
		HttpOnly: true,
		SameSite: http.SameSiteLaxMode,
		MaxAge:   -1,
	})
}

func (web *Web) WebRecoverPanic(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if err := recover(); err != nil {
				w.Header().Set("Connection", "close")
				web.App.LogError(r, fmt.Errorf("%s", err))
				http.Error(w, "Internal Server Error", http.StatusInternalServerError)
			}
		}()
		next.ServeHTTP(w, r)
	})
}

func (web *Web) WebRateLimit(next http.Handler) http.Handler {
	type client struct {
		limiter  *rate.Limiter
		lastSeen time.Time
	}

	var mu sync.Mutex
	clients := make(map[string]*client)

	go func() {
		for {
			time.Sleep(time.Minute)
			mu.Lock()
			for ip, c := range clients {
				if time.Since(c.lastSeen) > 3*time.Minute {
					delete(clients, ip)
				}
			}
			mu.Unlock()
		}
	}()

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if web.App.LimiterEnabled {
			ip, _, err := net.SplitHostPort(r.RemoteAddr)
			if err != nil {
				web.App.LogError(r, err)
				http.Error(w, "Internal Server Error", http.StatusInternalServerError)
				return
			}
			mu.Lock()
			if _, found := clients[ip]; !found {
				clients[ip] = &client{limiter: rate.NewLimiter(rate.Limit(web.App.LimiterRPS), web.App.LimiterBurst)}
			}
			clients[ip].lastSeen = time.Now()
			if !clients[ip].limiter.Allow() {
				mu.Unlock()
				http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
				return
			}
			mu.Unlock()
		}
		next.ServeHTTP(w, r)
	})
}
