package handlers

import (
	"bufio"
	"strings"
	"time"

	"github.com/gofiber/fiber/v2"

	helicopterfilerealtime "wucher/internal/realtime/helicopterfile"
	"wucher/internal/shared/pkg/uuidv7"
	responsedto "wucher/internal/transport/http/dto/response"
	"wucher/internal/transport/http/jsonapi"
)

const (
	helicopterFileSSEEventName = "helicopter.file.created"
)

type HelicopterFileEventsHandler struct {
	hub *helicopterfilerealtime.Hub
}

type helicopterFileSSEFilter struct {
	helicopterID *string
}

func NewHelicopterFileEventsHandler(hub *helicopterfilerealtime.Hub) *HelicopterFileEventsHandler {
	return &HelicopterFileEventsHandler{hub: hub}
}

func (h *HelicopterFileEventsHandler) Stream(c *fiber.Ctx) error {
	if h.hub == nil {
		return writeFileManagerError(c, fiber.StatusServiceUnavailable, "Service unavailable", "helicopter 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 := parseHelicopterFileSSEFilter(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 helicopter 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 := mapHelicopterFileCreatedEventToSSEPayload(event)
				if !writeSSEDataEvent(w, helicopterFileSSEEventName, payload) {
					return
				}
			}
		}
	})

	return nil
}

func parseHelicopterFileSSEFilter(c *fiber.Ctx) (helicopterFileSSEFilter, error) {
	filter := helicopterFileSSEFilter{}

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

	return filter, nil
}

func (f helicopterFileSSEFilter) match(event helicopterfilerealtime.Event) bool {
	helicopterID := strings.ToLower(strings.TrimSpace(event.HelicopterID))
	if helicopterID == "" {
		return false
	}
	if f.helicopterID != nil && helicopterID != *f.helicopterID {
		return false
	}
	return true
}

func mapHelicopterFileCreatedEventToSSEPayload(event helicopterfilerealtime.Event) responsedto.HelicopterFileCreatedSSEPayload {
	payload := responsedto.HelicopterFileCreatedSSEPayload{
		HelicopterID:      strings.TrimSpace(event.HelicopterID),
		HelicopterFileID:  strings.TrimSpace(event.HelicopterFileID),
		FileID:            strings.TrimSpace(event.FileID),
		Section:           strings.TrimSpace(event.Section),
		Position:          event.Position,
		IsMandatory:       event.IsMandatory,
		AttachmentPending: event.AttachmentPending,
		Name:              strings.TrimSpace(event.Name),
		MimeType:          strings.TrimSpace(event.MimeType),
		UpdatedAt:         strings.TrimSpace(event.UpdatedAt),
	}
	return payload
}
