package handlers

import (
	"bufio"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/gofiber/fiber/v2"

	filemanagerrealtime "wucher/internal/realtime/filemanager"
	"wucher/internal/shared/pkg/uuidv7"
	responsedto "wucher/internal/transport/http/dto/response"
	"wucher/internal/transport/http/jsonapi"
)

const (
	fileManagerSSEEventName = "file.updated"
)

type FileManagerEventsHandler struct {
	hub *filemanagerrealtime.Hub
}

type fileManagerSSEFilter struct {
	folderID *string
	fileIDs  map[string]struct{}
}

func NewFileManagerEventsHandler(hub *filemanagerrealtime.Hub) *FileManagerEventsHandler {
	return &FileManagerEventsHandler{hub: hub}
}

func (h *FileManagerEventsHandler) Stream(c *fiber.Ctx) error {
	if h.hub == nil {
		return writeFileManagerError(c, fiber.StatusServiceUnavailable, "Service unavailable", "file events stream is not configured", nil)
	}

	actorID := actorUserID(c)
	if len(actorID) != 16 {
		return writeFileManagerError(c, fiber.StatusUnauthorized, "Unauthorized", "invalid user context", nil)
	}
	userUUID, err := uuidv7.BytesToString(actorID)
	if err != nil {
		return writeFileManagerError(c, fiber.StatusUnauthorized, "Unauthorized", "invalid user context", nil)
	}

	filter, err := parseFileManagerSSEFilter(c)
	if err != nil {
		return err
	}

	sub, err := h.hub.SubscribeWithFilter(userUUID, filter.match)
	if err != nil {
		return writeFileManagerError(c, fiber.StatusServiceUnavailable, "Service unavailable", "cannot subscribe to file events stream", nil)
	}

	c.Set(fiber.HeaderContentType, "text/event-stream")
	c.Set(fiber.HeaderCacheControl, "no-cache, no-transform")
	c.Set(fiber.HeaderConnection, "keep-alive")
	c.Set("X-Accel-Buffering", "no")

	c.Context().SetBodyStreamWriter(func(w *bufio.Writer) {
		defer sub.Close()

		if !writeSSEComment(w, "connected") {
			return
		}

		heartbeat := time.NewTicker(20 * time.Second)
		defer heartbeat.Stop()

		for {
			select {
			case <-sub.Done():
				return
			case <-heartbeat.C:
				if !writeSSEComment(w, "ping") {
					return
				}
			case event, ok := <-sub.Events():
				if !ok {
					return
				}
				if !filter.match(event) {
					continue
				}
				payload := mapFileStatusEventToSSEPayload(event)
				if !writeSSEDataEvent(w, fileManagerSSEEventName, payload) {
					return
				}
			}
		}
	})

	return nil
}

func parseFileManagerSSEFilter(c *fiber.Ctx) (fileManagerSSEFilter, error) {
	filter := fileManagerSSEFilter{
		fileIDs: make(map[string]struct{}),
	}

	if rawFolderID := strings.TrimSpace(c.Query("folder_id")); rawFolderID != "" {
		parsed, err := uuidv7.ParseString(rawFolderID)
		if err != nil {
			return fileManagerSSEFilter{}, writeFileManagerError(c, fiber.StatusUnprocessableEntity, "Validation error", "folder_id is invalid UUID", &jsonapi.ErrorSource{Pointer: "/query/folder_id"})
		}
		canonical, err := uuidv7.BytesToString(parsed)
		if err != nil {
			return fileManagerSSEFilter{}, writeFileManagerError(c, fiber.StatusUnprocessableEntity, "Validation error", "folder_id is invalid UUID", &jsonapi.ErrorSource{Pointer: "/query/folder_id"})
		}
		canonical = strings.ToLower(strings.TrimSpace(canonical))
		filter.folderID = &canonical
	}

	rawTokens := make([]string, 0, 8)
	if rawFileID := strings.TrimSpace(c.Query("file_id")); rawFileID != "" {
		rawTokens = append(rawTokens, rawFileID)
	}
	if rawFileIDs := strings.TrimSpace(c.Query("file_ids")); rawFileIDs != "" {
		for _, token := range strings.Split(rawFileIDs, ",") {
			token = strings.TrimSpace(token)
			if token != "" {
				rawTokens = append(rawTokens, token)
			}
		}
	}
	for i := range rawTokens {
		parsed, err := uuidv7.ParseString(rawTokens[i])
		if err != nil {
			return fileManagerSSEFilter{}, writeFileManagerError(c, fiber.StatusUnprocessableEntity, "Validation error", "file_id/file_ids contains invalid UUID", &jsonapi.ErrorSource{Pointer: "/query/file_ids"})
		}
		canonical, err := uuidv7.BytesToString(parsed)
		if err != nil {
			return fileManagerSSEFilter{}, writeFileManagerError(c, fiber.StatusUnprocessableEntity, "Validation error", "file_id/file_ids contains invalid UUID", &jsonapi.ErrorSource{Pointer: "/query/file_ids"})
		}
		filter.fileIDs[strings.ToLower(strings.TrimSpace(canonical))] = struct{}{}
	}

	return filter, nil
}

func (f fileManagerSSEFilter) match(event filemanagerrealtime.FileStatusEvent) bool {
	fileUUID := strings.ToLower(strings.TrimSpace(event.FileUUID))
	if fileUUID == "" {
		return false
	}
	if len(f.fileIDs) > 0 {
		if _, ok := f.fileIDs[fileUUID]; !ok {
			return false
		}
	}
	if f.folderID != nil {
		if event.FolderUUID == nil {
			return false
		}
		eventFolderID := strings.ToLower(strings.TrimSpace(*event.FolderUUID))
		if eventFolderID == "" || !strings.EqualFold(eventFolderID, *f.folderID) {
			return false
		}
	}
	return true
}

func mapFileStatusEventToSSEPayload(event filemanagerrealtime.FileStatusEvent) responsedto.FileManagerFileUpdatedSSEPayload {
	payload := responsedto.FileManagerFileUpdatedSSEPayload{
		FileID:    strings.TrimSpace(event.FileUUID),
		Status:    strings.TrimSpace(event.Status),
		Name:      strings.TrimSpace(event.Name),
		MimeType:  strings.TrimSpace(event.MimeType),
		UpdatedAt: strings.TrimSpace(event.UpdatedAt),
	}
	if event.FolderUUID != nil {
		folderID := strings.TrimSpace(*event.FolderUUID)
		if folderID != "" {
			payload.FolderID = &folderID
		}
	}
	return payload
}

func writeSSEComment(w *bufio.Writer, comment string) bool {
	if _, err := fmt.Fprintf(w, ": %s\n\n", strings.TrimSpace(comment)); err != nil {
		return false
	}
	return w.Flush() == nil
}

func writeSSEDataEvent(w *bufio.Writer, eventName string, payload any) bool {
	raw, err := json.Marshal(payload)
	if err != nil {
		return false
	}
	if _, err := fmt.Fprintf(w, "event: %s\n", strings.TrimSpace(eventName)); err != nil {
		return false
	}
	if _, err := fmt.Fprintf(w, "data: %s\n\n", string(raw)); err != nil {
		return false
	}
	return w.Flush() == nil
}
