package middleware

import (
	"context"
	"encoding/json"
	"net/http"
	"regexp"
	"strings"

	"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/api/service"

	"go.uber.org/zap"
)

type contextKey string

const (
	UserIDKey    contextKey = "user_id"
	UserRoleKey  contextKey = "user_role"
	UserEmailKey contextKey = "user_email"
)

// WhitelistedEndpoints contains paths that can be accessed without authentication
var WhitelistedEndpoints = []*regexp.Regexp{
	// Study by UID
	regexp.MustCompile(`^/dicomWeb/studies\?.*StudyInstanceUID=.+`),

	// Frame endpoint
	regexp.MustCompile(`^/dicomWeb/studies/[^/]+/series/[^/]+/instances/[^/]+/frames/\d+$`),
}

// Auth middleware authenticates requests using JWT tokens
func Auth(authService *service.AuthService, logger *zap.Logger) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// Check if the request path is whitelisted
			path := r.URL.Path
			if r.URL.RawQuery != "" {
				path = path + "?" + r.URL.RawQuery
			}

			for _, pattern := range WhitelistedEndpoints {
				if pattern.MatchString(path) {
					// Path is whitelisted, skip authentication
					logger.Debug("Skipping authentication for whitelisted path", zap.String("path", path))
					next.ServeHTTP(w, r)
					return
				}
			}

			// Get authorization header
			authHeader := r.Header.Get("Authorization")
			if authHeader == "" {
				logger.Warn("Missing Authorization header")
				respondWithError(w, http.StatusUnauthorized, "missing authorization header")
				return
			}

			// Extract token from Bearer token
			bearerToken := strings.Split(authHeader, " ")
			if len(bearerToken) != 2 || strings.ToLower(bearerToken[0]) != "bearer" {
				logger.Warn("Invalid Authorization header format")
				respondWithError(w, http.StatusUnauthorized, "invalid authorization format")
				return
			}

			token := bearerToken[1]

			// Validate token
			claims, err := authService.ValidateToken(token)
			if err != nil {
				logger.Warn("Invalid or expired token", zap.Error(err))
				respondWithError(w, http.StatusUnauthorized, "invalid or expired token")
				return
			}

			// Check token type
			if claims.TokenType != "access" {
				logger.Warn("Invalid token type", zap.String("tokenType", claims.TokenType))
				respondWithError(w, http.StatusUnauthorized, "invalid token type")
				return
			}

			// Add user info to request context
			ctx := context.WithValue(r.Context(), UserIDKey, claims.UserID)
			ctx = context.WithValue(ctx, UserRoleKey, claims.Role)
			ctx = context.WithValue(ctx, UserEmailKey, claims.Email)

			// Log user info
			logger.Info("Authenticated user", zap.String("userID", claims.UserID), zap.String("role", claims.Role), zap.String("email", claims.Email))

			// Continue with the request
			next.ServeHTTP(w, r.WithContext(ctx))
		})
	}
}

// RoleRequired middleware checks if user has the required role
func RoleRequired(roles ...string) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// Check if the request path is whitelisted first
			path := r.URL.Path
			if r.URL.RawQuery != "" {
				path = path + "?" + r.URL.RawQuery
			}

			for _, pattern := range WhitelistedEndpoints {
				if pattern.MatchString(path) {
					// Path is whitelisted, skip role check
					next.ServeHTTP(w, r)
					return
				}
			}

			// Get user role from context
			userRole, ok := r.Context().Value(UserRoleKey).(string)
			if !ok {
				respondWithError(w, http.StatusUnauthorized, "user context not found")
				return
			}

			// Check if user has one of the required roles
			hasRole := false
			for _, role := range roles {
				if userRole == role {
					hasRole = true
					break
				}
			}

			if !hasRole {
				respondWithError(w, http.StatusForbidden, "insufficient permissions")
				return
			}

			// Continue with the request
			next.ServeHTTP(w, r)
		})
	}
}

// PatientViewRestriction middleware restricts patients to view only their studies
func PatientViewRestriction(logger *zap.Logger) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// Check if the request path is whitelisted first
			path := r.URL.Path
			if r.URL.RawQuery != "" {
				path = path + "?" + r.URL.RawQuery
			}

			for _, pattern := range WhitelistedEndpoints {
				if pattern.MatchString(path) {
					// Path is whitelisted, skip restrictions
					logger.Debug("Skipping patient restrictions for whitelisted path", zap.String("path", path))
					next.ServeHTTP(w, r)
					return
				}
			}

			// Get user role from context
			userRole, ok := r.Context().Value(UserRoleKey).(string)
			if !ok {
				respondWithError(w, http.StatusUnauthorized, "user context not found")
				return
			}

			// Only apply restrictions to patients
			if userRole != "patient" {
				next.ServeHTTP(w, r)
				return
			}

			// TODO: Logic to restrict patients to only access their assigned studies
			// For now, we're just letting the request through, but in a real
			// implementation, you would check the study ID against the patient's
			// assigned studies.

			// TODO: Check if the requested study is assigned to the patient
			// This would likely involve parsing the URL path to extract study ID
			// and checking it against a database of patient assignments

			next.ServeHTTP(w, r)
		})
	}
}

// Helper function to respond with JSON error
func respondWithError(w http.ResponseWriter, code int, message string) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(code)
	json.NewEncoder(w).Encode(map[string]string{"error": message})
}
