package ultidraw

import (
	"context"
	"fmt"
	"io"
	"net/url"
	"strings"
	"time"

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

func (s *Service) CreatePublicSession(ctx context.Context, token, filePath, mode, password, guestID, guestName, displayName string) (*PublicSessionResult, error) {
	if !s.Cfg.Enabled {
		return nil, fmt.Errorf("ultidraw editor disabled")
	}
	resolvedPath, err := s.resolvePublicFilePath(ctx, token, filePath, password, displayName)
	if err != nil {
		return nil, err
	}
	filePath = resolvedPath
	if mode == "" {
		mode = "edit"
	}

	ownerID, ownerPath, err := s.ownerPathForPublic(ctx, token, password, filePath, displayName)
	if err != nil {
		return nil, err
	}
	roomID, err := s.resolveCollabRoomID(ctx, ownerID, ownerPath)
	if err != nil {
		return nil, err
	}

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

	apiBase := strings.TrimRight(s.Cfg.APIInternalURL, "/")
	sig, _ := signPublicDocAccess(token, filePath, password, s.Cfg.HocuspocusSecret)
	docURL := fmt.Sprintf("%s/api/v1/drive/public/shares/%s/ultidraw/document?path=%s&password=%s&sig=%s",
		apiBase, url.PathEscape(token), url.QueryEscape(filePath), url.QueryEscape(password), url.QueryEscape(sig))
	saveURL := docURL

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

	return &PublicSessionResult{
		SessionResult: SessionResult{
			RoomID:        roomID,
			CanonicalPath: filePath,
			WsURL:         wsURL,
			Token:         tokenJWT,
			Mode:          mode,
			Collaboration: collab,
		},
		DocumentURL: docURL,
		SaveURL:     saveURL,
	}, nil
}

func (s *Service) resolvePublicFilePath(ctx context.Context, token, filePath, password, displayName string) (string, error) {
	filePath = normalizePath(filePath)
	if filePath == "/" {
		filePath = s.publicClientSourcePath(ctx, token, password, filePath, displayName)
	}
	checkPath := filePath
	if !isExcalidrawPath(checkPath) && strings.TrimSpace(displayName) != "" {
		checkPath = normalizePath("/" + strings.TrimSpace(displayName))
	}
	if !isExcalidrawPath(checkPath) {
		return "", fmt.Errorf("not an excalidraw file")
	}
	if !isExcalidrawPath(filePath) {
		filePath = checkPath
	}
	if _, err := s.publicFileExists(ctx, token, filePath, password); err != nil {
		if checkPath != filePath {
			if _, err2 := s.publicFileExists(ctx, token, checkPath, password); err2 != nil {
				return "", fmt.Errorf("file not found")
			}
			filePath = checkPath
		} else {
			return "", fmt.Errorf("file not found")
		}
	}
	return filePath, nil
}

func (s *Service) publicClientSourcePath(ctx context.Context, token, password, clientPath, displayName string) string {
	binding, err := s.nc.ResolvePublicShareBinding(ctx, token, password)
	if err == nil {
		return binding.ClientSourcePath(clientPath, displayName)
	}
	if name := strings.TrimSpace(displayName); name != "" {
		return normalizePath("/" + name)
	}
	return clientPath
}

func (s *Service) publicFileExists(ctx context.Context, token, path, password string) (bool, error) {
	_, err := s.nc.PublicShareFileRevision(ctx, token, path, password)
	if err != nil {
		return false, err
	}
	return true, nil
}

func (s *Service) LoadPublicDocument(ctx context.Context, token, clientPath, password, displayName string) ([]byte, error) {
	if binding, err := s.nc.ResolvePublicShareBinding(ctx, token, password); err == nil {
		ownerPath := binding.OwnerPathForClient(clientPath, displayName)
		body, _, err := s.nc.Download(ctx, binding.OwnerID, ownerPath)
		if err == nil {
			defer body.Close()
			return io.ReadAll(body)
		}
	}
	body, _, err := s.nc.DownloadPublicShare(ctx, token, clientPath, password)
	if err != nil {
		return nil, err
	}
	defer body.Close()
	return io.ReadAll(body)
}

func (s *Service) SavePublicDocument(ctx context.Context, token, clientPath, password, displayName string, raw []byte) error {
	if binding, err := s.nc.ResolvePublicShareBinding(ctx, token, password); err == nil {
		ownerPath := binding.OwnerPathForClient(clientPath, displayName)
		reader := strings.NewReader(string(raw))
		if err := s.nc.Upload(ctx, binding.OwnerID, ownerPath, reader, "application/json"); err == nil {
			return nil
		}
	}
	reader := strings.NewReader(string(raw))
	return s.nc.UploadPublicShare(ctx, token, clientPath, password, reader, "application/json")
}

func (s *Service) LoadPublicDocumentLegacy(ctx context.Context, token, path, password string) ([]byte, error) {
	return s.LoadPublicDocument(ctx, token, path, password, "")
}

func (s *Service) SavePublicDocumentLegacy(ctx context.Context, token, path, password string, raw []byte) error {
	return s.SavePublicDocument(ctx, token, path, password, "", raw)
}

func (s *Service) EffectivePublicSharePermissions(ctx context.Context, token, path, password string) (int, error) {
	return s.nc.EffectivePublicSharePermissions(ctx, token, path, password)
}

func (s *Service) PublicShareCanUpdate(perms int) bool {
	return nextcloud.PublicShareCanUpdate(perms)
}
