package service

import (
	"context"
	"crypto/md5"
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"errors"
	"eslogad-be/config"
	"eslogad-be/internal/appcontext"
	"eslogad-be/internal/contract"
	"eslogad-be/internal/entities"
	"eslogad-be/internal/processor"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/golang-jwt/jwt/v5"
	"github.com/google/uuid"
	"gorm.io/gorm"
)

type OnlyOfficeService interface {
	ProcessCallback(ctx context.Context, documentKey string, req *contract.OnlyOfficeCallbackRequest) (*contract.OnlyOfficeCallbackResponse, error)
	GetEditorConfig(ctx context.Context, req *contract.GetEditorConfigRequest) (*contract.GetEditorConfigResponse, error)
	LockDocument(ctx context.Context, documentID uuid.UUID, userID uuid.UUID) error
	UnlockDocument(ctx context.Context, documentID uuid.UUID, userID uuid.UUID) error
	GetDocumentSession(ctx context.Context, documentKey string) (*contract.DocumentSession, error)
	GetOnlyOfficeConfig(ctx context.Context) (*contract.OnlyOfficeConfigInfo, error)
}

type OnlyOfficeServiceImpl struct {
	processor       processor.OnlyOfficeProcessor
	documentBaseURL string
	callbackBaseURL string
	serverURL       string
	jwtSecret       string
	config          *config.OnlyOffice
	db              *gorm.DB
	fileStorage     FileStorage
	docBucket       string
}

func NewOnlyOfficeService(processor processor.OnlyOfficeProcessor, cfg *config.OnlyOffice, db *gorm.DB, fileStorage FileStorage) *OnlyOfficeServiceImpl {
	return &OnlyOfficeServiceImpl{
		processor:       processor,
		documentBaseURL: getEnvOrDefault("DOCUMENT_BASE_URL", "https://68878e421f6d.ngrok-free.app/api/v1/files"),
		callbackBaseURL: getEnvOrDefault("CALLBACK_BASE_URL", "https://b4ed0a70d9d6.ngrok-free.app/api/v1/onlyoffice/callback"),
		serverURL:       cfg.URL,
		jwtSecret:       cfg.Token,
		config:          cfg,
		db:              db,
		fileStorage:     fileStorage,
		docBucket:       "documents", // Use the same bucket as document uploads
	}
}

func getEnvOrDefault(key, defaultValue string) string {
	if value := os.Getenv(key); value != "" {
		return value
	}
	return defaultValue
}

func (s *OnlyOfficeServiceImpl) ProcessCallback(ctx context.Context, documentKey string, req *contract.OnlyOfficeCallbackRequest) (*contract.OnlyOfficeCallbackResponse, error) {
	// Verify JWT token if provided and secret is configured
	if req.Token != "" && s.jwtSecret != "" {
		claims, err := s.verifyJWT(req.Token)
		if err != nil {
			// Log the error but continue processing
			// OnlyOffice may not always send valid tokens
			fmt.Printf("JWT verification failed: %v\n", err)
		} else if claims != nil {
			// Extract data from JWT claims if needed
			if key, ok := claims["key"].(string); ok && key != documentKey {
				return &contract.OnlyOfficeCallbackResponse{Error: 1}, fmt.Errorf("document key mismatch in JWT")
			}
		}
	}

	session, err := s.processor.GetDocumentSessionByKey(ctx, documentKey)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return &contract.OnlyOfficeCallbackResponse{Error: 1}, nil // Document key not found
		}
		return &contract.OnlyOfficeCallbackResponse{Error: 3}, err // Internal server error
	}

	// Process based on status
	switch req.Status {
	case contract.OnlyOfficeStatusEditing:
		// Document is being edited
		err = s.handleEditingStatus(ctx, session, req)

	case contract.OnlyOfficeStatusReady:
		// Document is ready for saving
		err = s.handleReadyStatus(ctx, session, req)

	case contract.OnlyOfficeStatusSaveError:
		// Document saving error
		err = s.handleSaveError(ctx, session, req)

	case contract.OnlyOfficeStatusClosed:
		// Document closed with no changes
		err = s.handleClosedStatus(ctx, session, req)

	case contract.OnlyOfficeStatusForceSave:
		// Force save during editing
		err = s.handleForceSave(ctx, session, req)

	case contract.OnlyOfficeStatusForceSaveError:
		// Force save error
		err = s.handleForceSaveError(ctx, session, req)

	default:
		return &contract.OnlyOfficeCallbackResponse{Error: 3}, fmt.Errorf("unknown status: %d", req.Status)
	}

	if err != nil {
		return &contract.OnlyOfficeCallbackResponse{Error: 3}, err
	}

	return &contract.OnlyOfficeCallbackResponse{Error: 0}, nil
}

// handleEditingStatus handles when document is being edited
func (s *OnlyOfficeServiceImpl) handleEditingStatus(ctx context.Context, session *entities.DocumentSession, req *contract.OnlyOfficeCallbackRequest) error {
	// Update session status
	session.Status = req.Status

	// Lock document if not already locked
	if !session.IsLocked && len(req.Users) > 0 {
		userID := getOnlyOfficeUserIDFromContext(ctx)
		session.IsLocked = true
		session.LockedBy = &userID
		now := time.Now()
		session.LockedAt = &now
	}

	return s.processor.UpdateDocumentSession(ctx, session)
}

// handleReadyStatus handles when document is ready for saving
func (s *OnlyOfficeServiceImpl) handleReadyStatus(ctx context.Context, session *entities.DocumentSession, req *contract.OnlyOfficeCallbackRequest) error {
	if req.URL == "" {
		return errors.New("document URL is required for saving")
	}

	// Download the document
	documentData, err := s.downloadDocument(req.URL)
	if err != nil {
		return fmt.Errorf("failed to download document: %w", err)
	}

	// Use session UserID as SavedBy since callbacks don't have user context
	savedBy := session.UserID
	if savedBy == uuid.Nil {
		// Fallback to getting from context if available
		if userID := getOnlyOfficeUserIDFromContext(ctx); userID != uuid.Nil {
			savedBy = userID
		}
	}

	// Save new version
	version := &entities.DocumentVersion{
		DocumentID: session.DocumentID,
		Version:    session.Version + 1,
		FileSize:   int64(len(documentData)),
		SavedBy:    savedBy,
		SavedAt:    time.Now(),
		IsActive:   true,
	}

	// For now, default to outgoing_attachment
	// In production, this should be stored in the session or document metadata
	documentType := "outgoing_attachment"

	// Generate new file path and save
	fileName := fmt.Sprintf("v%d_%s_%s.docx", version.Version, time.Now().Format("20060102150405"), session.DocumentKey)
	filePath, err := s.saveDocumentFile(ctx, documentData, session.DocumentID, fileName, documentType)
	if err != nil {
		return fmt.Errorf("failed to save document file: %w", err)
	}
	version.FileURL = filePath

	// Save changes URL if provided
	if req.ChangesURL != "" {
		version.ChangesURL = &req.ChangesURL
	}

	// Create new version
	err = s.processor.CreateDocumentVersion(ctx, version)
	if err != nil {
		return fmt.Errorf("failed to create document version: %w", err)
	}

	// Update session
	session.Status = req.Status
	session.Version = version.Version
	now := time.Now()
	session.LastSavedAt = &now
	session.IsLocked = false
	session.LockedBy = nil
	session.LockedAt = nil

	// Update the original document reference with new URL
	err = s.processor.UpdateDocumentURL(ctx, session.DocumentID, version.FileURL)
	if err != nil {
		return fmt.Errorf("failed to update document URL: %w", err)
	}

	return s.processor.UpdateDocumentSession(ctx, session)
}

// handleSaveError handles document save errors
func (s *OnlyOfficeServiceImpl) handleSaveError(ctx context.Context, session *entities.DocumentSession, req *contract.OnlyOfficeCallbackRequest) error {
	// Log the error
	s.processor.LogDocumentError(ctx, session.DocumentID, "Save error occurred", req)

	// Update session status
	session.Status = req.Status
	return s.processor.UpdateDocumentSession(ctx, session)
}

// handleClosedStatus handles when document is closed without changes
func (s *OnlyOfficeServiceImpl) handleClosedStatus(ctx context.Context, session *entities.DocumentSession, req *contract.OnlyOfficeCallbackRequest) error {
	// Unlock document
	session.Status = req.Status
	session.IsLocked = false
	session.LockedBy = nil
	session.LockedAt = nil

	return s.processor.UpdateDocumentSession(ctx, session)
}

func (s *OnlyOfficeServiceImpl) handleForceSave(ctx context.Context, session *entities.DocumentSession, req *contract.OnlyOfficeCallbackRequest) error {
	if req.URL == "" {
		return errors.New("document URL is required for force save")
	}

	documentData, err := s.downloadDocument(req.URL)
	if err != nil {
		return fmt.Errorf("failed to download document: %w", err)
	}

	savedBy := session.UserID
	if savedBy == uuid.Nil {
		if userID := getOnlyOfficeUserIDFromContext(ctx); userID != uuid.Nil {
			savedBy = userID
		}
	}

	version := &entities.DocumentVersion{
		DocumentID: session.DocumentID,
		Version:    session.Version + 1,
		FileSize:   int64(len(documentData)),
		SavedBy:    savedBy,
		SavedAt:    time.Now(),
		IsActive:   false,
		Comments:   stringPtr("Auto-save during editing"),
	}

	// For now, default to outgoing_attachment
	// In production, this should be stored in the session or document metadata
	documentType := "outgoing_attachment"

	fileName := fmt.Sprintf("autosave_v%d_%s_%s.docx", version.Version, time.Now().Format("20060102150405"), session.DocumentKey)
	filePath, err := s.saveDocumentFile(ctx, documentData, session.DocumentID, fileName, documentType)
	if err != nil {
		return fmt.Errorf("failed to save document file: %w", err)
	}
	version.FileURL = filePath

	err = s.processor.CreateDocumentVersion(ctx, version)
	if err != nil {
		return fmt.Errorf("failed to create document version: %w", err)
	}

	now := time.Now()
	session.LastSavedAt = &now
	session.Version = version.Version

	return s.processor.UpdateDocumentSession(ctx, session)
}

// handleForceSaveError handles force save errors
func (s *OnlyOfficeServiceImpl) handleForceSaveError(ctx context.Context, session *entities.DocumentSession, req *contract.OnlyOfficeCallbackRequest) error {
	// Log the error
	s.processor.LogDocumentError(ctx, session.DocumentID, "Force save error occurred", req)

	// Update session status
	session.Status = req.Status
	return s.processor.UpdateDocumentSession(ctx, session)
}

// downloadDocument downloads document from OnlyOffice
func (s *OnlyOfficeServiceImpl) downloadDocument(url string) ([]byte, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to download document: status %d", resp.StatusCode)
	}

	return io.ReadAll(resp.Body)
}

// saveDocumentFile saves document file to S3 storage
func (s *OnlyOfficeServiceImpl) saveDocumentFile(ctx context.Context, data []byte, documentID uuid.UUID, fileName string, documentType string) (string, error) {
	// Ensure bucket exists
	if err := s.fileStorage.EnsureBucket(ctx, s.docBucket); err != nil {
		return "", fmt.Errorf("failed to ensure bucket: %w", err)
	}

	// Create S3 key with date structure
	dateDir := time.Now().Format("2006/01/02")
	key := fmt.Sprintf("onlyoffice/%s/%s/%s", documentID.String(), dateDir, fileName)

	// Detect content type from file extension
	contentType := "application/octet-stream"
	ext := filepath.Ext(fileName)
	switch strings.ToLower(ext) {
	case ".docx":
		contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
	case ".doc":
		contentType = "application/msword"
	case ".xlsx":
		contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
	case ".xls":
		contentType = "application/vnd.ms-excel"
	case ".pptx":
		contentType = "application/vnd.openxmlformats-officedocument.presentationml.presentation"
	case ".ppt":
		contentType = "application/vnd.ms-powerpoint"
	case ".pdf":
		contentType = "application/pdf"
	}

	// Upload to S3
	url, err := s.fileStorage.Upload(ctx, s.docBucket, key, data, contentType)
	if err != nil {
		return "", fmt.Errorf("failed to upload to S3: %w", err)
	}

	// Now update the attachment URL in the database
	if err := s.updateAttachmentURL(ctx, documentID, url, documentType); err != nil {
		// Log error but don't fail - the document is already saved
		fmt.Printf("Warning: Failed to update attachment URL: %v\n", err)
	}

	return url, nil
}

// updateAttachmentURL updates the file_url in the appropriate attachment table
func (s *OnlyOfficeServiceImpl) updateAttachmentURL(ctx context.Context, attachmentID uuid.UUID, newURL string, documentType string) error {
	switch documentType {
	case "letter_outgoing_attachment", "outgoing_attachment":
		return s.db.WithContext(ctx).
			Table("letter_outgoing_attachments").
			Where("id = ?", attachmentID).
			Update("file_url", newURL).Error

	case "letter_incoming_attachment", "incoming_attachment":
		return s.db.WithContext(ctx).
			Table("letter_incoming_attachments").
			Where("id = ?", attachmentID).
			Update("file_url", newURL).Error

	default:
		return fmt.Errorf("unsupported document type for URL update: %s", documentType)
	}
}

// getDocumentFromAttachment retrieves document details directly from attachment tables
func (s *OnlyOfficeServiceImpl) getDocumentFromAttachment(ctx context.Context, documentID uuid.UUID, documentType string) (*processor.DocumentDetails, error) {
	var fileName, fileURL, fileType string
	var fileSize int64

	switch documentType {
	case "letter_outgoing_attachment", "outgoing_attachment":
		var attachment struct {
			FileName string `gorm:"column:file_name"`
			FileURL  string `gorm:"column:file_url"`
			FileType string `gorm:"column:file_type"`
		}

		err := s.db.WithContext(ctx).
			Table("letter_outgoing_attachments").
			Where("id = ?", documentID).
			Select("file_name, file_url, file_type").
			First(&attachment).Error

		if err != nil {
			return nil, fmt.Errorf("failed to get outgoing attachment: %w", err)
		}

		fileName = attachment.FileName
		fileURL = attachment.FileURL
		fileType = attachment.FileType

	case "letter_incoming_attachment", "incoming_attachment":
		var attachment struct {
			FileName string `gorm:"column:file_name"`
			FileURL  string `gorm:"column:file_url"`
			FileType string `gorm:"column:file_type"`
		}

		err := s.db.WithContext(ctx).
			Table("letter_incoming_attachments").
			Where("id = ?", documentID).
			Select("file_name, file_url, file_type").
			First(&attachment).Error

		if err != nil {
			return nil, fmt.Errorf("failed to get incoming attachment: %w", err)
		}

		fileName = attachment.FileName
		fileURL = attachment.FileURL
		fileType = attachment.FileType

	default:
		return nil, fmt.Errorf("unsupported document type: %s", documentType)
	}

	return &processor.DocumentDetails{
		DocumentID:   documentID,
		FileName:     fileName,
		FileType:     fileType,
		FileURL:      fileURL,
		FileSize:     fileSize,
		DocumentType: documentType,
		ReferenceID:  documentID,
	}, nil
}

// GetEditorConfig generates OnlyOffice editor configuration
func (s *OnlyOfficeServiceImpl) GetEditorConfig(ctx context.Context, req *contract.GetEditorConfigRequest) (*contract.GetEditorConfigResponse, error) {
	userCtx := appcontext.FromGinContext(ctx)
	if userCtx == nil {
		return nil, errors.New("user context not found")
	}

	session, err := s.processor.GetOrCreateDocumentSession(ctx, req.DocumentID, userCtx.UserID)
	if err != nil {
		return nil, fmt.Errorf("failed to get document session: %w", err)
	}

	// Get document details directly from attachment tables
	document, err := s.getDocumentFromAttachment(ctx, req.DocumentID, req.DocumentType)
	if err != nil {
		return nil, fmt.Errorf("failed to get document details: %w", err)
	}

	documentKey := session.DocumentKey

	fileExt := s.getFileExtension(document.FileName)
	if fileExt == "" || fileExt == strings.ToLower(document.FileName) {
		fileExt = s.getFileExtension(document.FileType)
	}

	ooType := "desktop"
	if req.DocumentType == "incoming_attachment" {
		ooType = "embedded"
	}

	config := &contract.OnlyOfficeConfigRequest{
		Document: &contract.OnlyOfficeDocument{
			FileType: fileExt,
			Key:      documentKey,
			Title:    document.FileName,
			URL:      document.FileURL,
			Permissions: &contract.OnlyOfficePermissions{
				Comment:   true,
				Download:  true,
				Edit:      req.Mode == "edit",
				FillForms: true,
				Print:     true,
				Review:    req.Mode == "edit",
			},
			Info: &contract.OnlyOfficeDocumentInfo{
				Owner:    fmt.Sprintf("User-%s", userCtx.UserID.String()[:8]),
				Uploaded: time.Now().Format("2006-01-02 15:04:05"),
			},
		},
		DocumentType: s.getDocumentType(fileExt), // Convert file extension to document type
		EditorConfig: &contract.OnlyOfficeEditorConfig{
			CallbackURL: fmt.Sprintf("%s/%s", s.callbackBaseURL, documentKey),
			Lang:        "en",
			Mode:        req.Mode,
			User: &contract.OnlyOfficeUserConfig{
				ID:   userCtx.UserID.String(),
				Name: userCtx.UserName,
			},
			Customization: &contract.OnlyOfficeCustomization{
				Autosave:      true,
				Comments:      true,
				CompactHeader: false,
				ForceSave:     true,
				Zoom:          100,
			},
		},
		Type: ooType, // Can be desktop, mobile, or embedded
	}

	if s.jwtSecret != "" {
		token, err := s.generateJWT(config)
		if err != nil {
			return nil, fmt.Errorf("failed to generate JWT: %w", err)
		}
		config.Token = token
	}

	return &contract.GetEditorConfigResponse{
		DocumentServerURL: s.serverURL,
		Config:            config,
	}, nil
}

// LockDocument locks a document for editing
func (s *OnlyOfficeServiceImpl) LockDocument(ctx context.Context, documentID uuid.UUID, userID uuid.UUID) error {
	return s.processor.LockDocument(ctx, documentID, userID)
}

// UnlockDocument unlocks a document
func (s *OnlyOfficeServiceImpl) UnlockDocument(ctx context.Context, documentID uuid.UUID, userID uuid.UUID) error {
	return s.processor.UnlockDocument(ctx, documentID, userID)
}

// GetDocumentSession gets document session by key
func (s *OnlyOfficeServiceImpl) GetDocumentSession(ctx context.Context, documentKey string) (*contract.DocumentSession, error) {
	session, err := s.processor.GetDocumentSessionByKey(ctx, documentKey)
	if err != nil {
		return nil, err
	}

	return &contract.DocumentSession{
		ID:          session.ID,
		DocumentID:  session.DocumentID,
		DocumentKey: session.DocumentKey,
		UserID:      session.UserID,
		Status:      session.Status,
		IsLocked:    session.IsLocked,
		LockedBy:    session.LockedBy,
		LockedAt:    session.LockedAt,
		LastSavedAt: session.LastSavedAt,
		Version:     session.Version,
		CreatedAt:   session.CreatedAt,
		UpdatedAt:   session.UpdatedAt,
	}, nil
}

// generateDocumentKey generates a unique key for OnlyOffice
func (s *OnlyOfficeServiceImpl) generateDocumentKey(documentID uuid.UUID, version int) string {
	// Use nanoseconds and random bytes for uniqueness
	randomBytes := make([]byte, 8)
	rand.Read(randomBytes)
	data := fmt.Sprintf("%s_%d_%d_%s", documentID.String(), version, time.Now().UnixNano(), hex.EncodeToString(randomBytes))
	hash := md5.Sum([]byte(data))
	return hex.EncodeToString(hash[:])
}

// getFileExtension extracts the file extension from file type or filename
func (s *OnlyOfficeServiceImpl) getFileExtension(fileType string) string {
	// Remove any leading dot
	fileType = strings.TrimPrefix(fileType, ".")

	// If fileType contains a dot, extract the extension after the last dot
	if strings.Contains(fileType, ".") {
		parts := strings.Split(fileType, ".")
		if len(parts) > 1 {
			return strings.ToLower(parts[len(parts)-1])
		}
	}

	// Otherwise, return as is (assuming it's already an extension like "docx", "xlsx", etc.)
	return strings.ToLower(fileType)
}

// getDocumentType determines OnlyOffice document type from file extension
func (s *OnlyOfficeServiceImpl) getDocumentType(fileType string) string {
	fileType = strings.ToLower(fileType)

	// Remove dot if present
	fileType = strings.TrimPrefix(fileType, ".")

	// Text documents
	if fileType == "doc" || fileType == "docx" || fileType == "docm" ||
		fileType == "dot" || fileType == "dotx" || fileType == "dotm" ||
		fileType == "odt" || fileType == "fodt" || fileType == "ott" ||
		fileType == "rtf" || fileType == "txt" || fileType == "html" ||
		fileType == "htm" || fileType == "mht" || fileType == "pdf" ||
		fileType == "djvu" || fileType == "fb2" || fileType == "epub" ||
		fileType == "xps" {
		return "word"
	}

	// Spreadsheets
	if fileType == "xls" || fileType == "xlsx" || fileType == "xlsm" ||
		fileType == "xlt" || fileType == "xltx" || fileType == "xltm" ||
		fileType == "ods" || fileType == "fods" || fileType == "ots" ||
		fileType == "csv" {
		return "cell"
	}

	// Presentations
	if fileType == "pps" || fileType == "ppsx" || fileType == "ppsm" ||
		fileType == "ppt" || fileType == "pptx" || fileType == "pptm" ||
		fileType == "pot" || fileType == "potx" || fileType == "potm" ||
		fileType == "odp" || fileType == "fodp" || fileType == "otp" {
		return "presentation"
	}

	// Default to text
	return "slide"
}

// generateJWT generates JWT token for OnlyOffice
func (s *OnlyOfficeServiceImpl) generateJWT(config *contract.OnlyOfficeConfigRequest) (string, error) {
	// OnlyOffice expects the entire config to be in the JWT payload
	payload := make(map[string]interface{})

	// Convert the config struct to a map for JWT payload
	configJSON, err := json.Marshal(config)
	if err != nil {
		return "", fmt.Errorf("failed to marshal config: %w", err)
	}

	var configMap map[string]interface{}
	if err := json.Unmarshal(configJSON, &configMap); err != nil {
		return "", fmt.Errorf("failed to unmarshal config to map: %w", err)
	}

	// Add all config fields to the payload
	for key, value := range configMap {
		payload[key] = value
	}

	// Create the token with the payload
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(payload))

	// Sign the token with the secret
	tokenString, err := token.SignedString([]byte(s.jwtSecret))
	if err != nil {
		return "", fmt.Errorf("failed to sign JWT token: %w", err)
	}

	return tokenString, nil
}

func getOnlyOfficeUserIDFromContext(ctx context.Context) uuid.UUID {
	userCtx := appcontext.FromGinContext(ctx)
	if userCtx != nil {
		return userCtx.UserID
	}
	return uuid.Nil
}

func stringPtr(s string) *string {
	return &s
}

// GetOnlyOfficeConfig returns the OnlyOffice configuration
func (s *OnlyOfficeServiceImpl) GetOnlyOfficeConfig(ctx context.Context) (*contract.OnlyOfficeConfigInfo, error) {
	return &contract.OnlyOfficeConfigInfo{
		URL:   s.config.URL,
		Token: s.config.Token,
	}, nil
}

// verifyJWT verifies JWT token from OnlyOffice
func (s *OnlyOfficeServiceImpl) verifyJWT(tokenString string) (jwt.MapClaims, error) {
	if s.jwtSecret == "" {
		// If no secret is configured, skip verification
		return nil, nil
	}

	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		// Validate the signing method
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}
		return []byte(s.jwtSecret), nil
	})

	if err != nil {
		return nil, fmt.Errorf("failed to parse JWT: %w", err)
	}

	if !token.Valid {
		return nil, errors.New("invalid JWT token")
	}

	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		return nil, errors.New("failed to parse JWT claims")
	}

	return claims, nil
}
