package authmw

import (
	"context"
	"crypto/x509"
	"encoding/json"
	"fmt"
	"net/http"
	"slices"
	"strings"
	"time"

	"git.t-juice.club/microfilm/auth"
	"github.com/golang-jwt/jwt/v5"
	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)

func VerifyToken(authURL string, permittedRoles []string) func(http.Handler) http.Handler {
	fn := func(next http.Handler) http.Handler {
		fn := func(w http.ResponseWriter, r *http.Request) {
			authHeader := r.Header.Get("Authorization")
			if !strings.Contains(authHeader, "Bearer ") {
				// No token, pass if unathorized in permitted
				// else reject
				if slices.Contains[[]string, string](permittedRoles, auth.RoleUnauthorized) {
					next.ServeHTTP(w, r)
					return
				}

				// Reject and write error response
				w.WriteHeader(http.StatusUnauthorized)
				var errResp auth.ErrorResponse
				errResp.Message = fmt.Sprintf("Authorization required: %s", strings.Join(permittedRoles, ","))
				errResp.Status = http.StatusUnauthorized

				encoder := json.NewEncoder(w)
				_ = encoder.Encode(&errResp)
				return
			}

			// Fetch current pubkey
			url := fmt.Sprintf("%s/key", authURL)
			ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
			defer cancel()

			req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
			if err != nil {
				// TODO: Should log
				w.WriteHeader(http.StatusInternalServerError)
				errResp := &auth.ErrorResponse{
					Message: fmt.Sprintf("Error getting pubkey for token verification: %s", err),
					Status:  http.StatusUnauthorized,
				}

				encoder := json.NewEncoder(w)
				_ = encoder.Encode(&errResp)
				return
			}

			client := &http.Client{
				Transport: otelhttp.NewTransport(http.DefaultTransport),
			}

			resp, err := client.Do(req)
			if err != nil {
				// TODO: Should log
				w.WriteHeader(http.StatusInternalServerError)
				errResp := &auth.ErrorResponse{
					Message: fmt.Sprintf("Error getting pubkey for token verification: %s", err),
					Status:  http.StatusUnauthorized,
				}

				encoder := json.NewEncoder(w)
				_ = encoder.Encode(&errResp)
				return
			}
			defer resp.Body.Close()

			var authResponse auth.PubkeyResponse
			decoder := json.NewDecoder(resp.Body)
			if err := decoder.Decode(&authResponse); err != nil {
				// TODO: Should log
				w.WriteHeader(http.StatusInternalServerError)
				errResp := &auth.ErrorResponse{
					Message: fmt.Sprintf("Error getting pubkey for token verification: %s", err),
					Status:  http.StatusUnauthorized,
				}

				encoder := json.NewEncoder(w)
				_ = encoder.Encode(&errResp)
				return
			}

			// Parse pubkey
			pub, err := x509.ParsePKIXPublicKey(authResponse.PubKey)
			if err != nil {
				// TODO: Should log
				w.WriteHeader(http.StatusInternalServerError)
				errResp := &auth.ErrorResponse{
					Message: fmt.Sprintf("Error getting pubkey for token verification: %s", err),
					Status:  http.StatusUnauthorized,
				}

				encoder := json.NewEncoder(w)
				_ = encoder.Encode(&errResp)
				return
			}

			// Validate token
			tokenString := strings.Split(authHeader, " ")[1]
			token, err := jwt.ParseWithClaims(tokenString, &auth.MicrofilmClaims{}, func(t *jwt.Token) (interface{}, error) { return pub, nil })
			if err != nil {
				// Reject and write error response
				w.WriteHeader(http.StatusUnauthorized)
				var errResp auth.ErrorResponse
				errResp.Message = fmt.Sprintf("Token verification failed: %s", err)
				errResp.Status = http.StatusUnauthorized

				encoder := json.NewEncoder(w)
				_ = encoder.Encode(&errResp)
				return
			}
			// TODO: Check that it is in permitted

			// Add claims to request context
			if claims, ok := token.Claims.(*auth.MicrofilmClaims); ok && token.Valid {
				ctx := context.WithValue(r.Context(), "claims", claims)
				next.ServeHTTP(w, r.WithContext(ctx))
				return
			}

			next.ServeHTTP(w, r)
		}
		return http.HandlerFunc(fn)
	}

	return fn
}