package ultidraw

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"strings"
	"time"

	"github.com/ultisuite/ulti-backend/internal/auth"
	"github.com/ultisuite/ulti-backend/internal/nextcloud"
)

type Service struct {
	nc  *nextcloud.Client
	Cfg Config
	hub fileChangePublisher
}

type fileChangePublisher interface {
	PublishFileChanged(platformUserID, path string)
}

func NewService(nc *nextcloud.Client, cfg Config, hub fileChangePublisher) *Service {
	return &Service{nc: nc, Cfg: cfg, hub: hub}
}

func (s *Service) EnsureNextcloudUser(ctx context.Context, claims *auth.Claims) (string, error) {
	return s.nc.EnsurePrincipal(ctx, claims.Email, claims.Sub, claims.Name)
}

type SessionResult struct {
	RoomID         string `json:"roomId"`
	CanonicalPath  string `json:"canonicalPath"`
	WsURL          string `json:"wsUrl"`
	Token          string `json:"token"`
	Mode           string `json:"mode"`
	Collaboration  bool   `json:"collaboration"`
}

type PublicSessionResult struct {
	SessionResult
	DocumentURL string `json:"documentUrl,omitempty"`
	SaveURL     string `json:"saveUrl,omitempty"`
}

func (s *Service) CreateSession(ctx context.Context, ncUser, filePath, mode, editorUserID, editorName string) (*SessionResult, error) {
	if !s.Cfg.Enabled {
		return nil, fmt.Errorf("ultidraw editor disabled")
	}
	filePath = normalizePath(filePath)
	if !isExcalidrawPath(filePath) {
		return nil, fmt.Errorf("not an excalidraw file: %s", filePath)
	}
	if _, err := s.nc.FileRevision(ctx, ncUser, filePath); err != nil {
		return nil, fmt.Errorf("file not found: %s", filePath)
	}
	if mode == "" {
		mode = "edit"
	}

	roomID, err := s.resolveCollabRoomID(ctx, ncUser, filePath)
	if err != nil {
		return nil, err
	}

	token, err := signRoomToken(roomTokenPayload{
		Room:    roomID,
		Path:    filePath,
		User:    ncUser,
		Sub:     editorUserID,
		Name:    editorName,
		Mode:    mode,
		Expires: time.Now().Add(8 * time.Hour).Unix(),
	}, s.Cfg.HocuspocusSecret)
	if err != nil {
		return nil, err
	}

	wsURL := strings.TrimSpace(s.Cfg.HocuspocusPublicURL)
	collab := wsURL != "" && s.Cfg.HocuspocusSecret != ""

	return &SessionResult{
		RoomID:        roomID,
		CanonicalPath: filePath,
		WsURL:         wsURL,
		Token:         token,
		Mode:          mode,
		Collaboration: collab,
	}, nil
}

func (s *Service) LoadDocument(ctx context.Context, ncUser, path string) ([]byte, error) {
	path = normalizePath(path)
	body, _, err := s.nc.Download(ctx, ncUser, path)
	if err != nil {
		return nil, err
	}
	defer body.Close()
	return io.ReadAll(body)
}

func (s *Service) LoadDocumentForUser(ctx context.Context, ncUser, path string) ([]byte, error) {
	path = normalizePath(path)
	if strings.HasPrefix(ncUser, "public:") {
		token := strings.TrimPrefix(ncUser, "public:")
		return s.LoadPublicDocumentLegacy(ctx, token, path, "")
	}
	return s.LoadDocument(ctx, ncUser, path)
}

func (s *Service) SaveDocument(ctx context.Context, ncUser, path string, raw []byte, platformUserID string) error {
	path = normalizePath(path)
	reader := bytes.NewReader(raw)
	if strings.HasPrefix(ncUser, "public:") {
		token := strings.TrimPrefix(ncUser, "public:")
		return s.SavePublicDocumentLegacy(ctx, token, path, "", raw)
	}
	if err := s.nc.Upload(ctx, ncUser, path, reader, "application/json"); err != nil {
		return err
	}
	if s.hub != nil && platformUserID != "" {
		s.hub.PublishFileChanged(platformUserID, path)
	}
	return nil
}
