package drive

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"io"

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

func shouldSyncUltidocSidecar(path string) bool {
	return !nextcloud.IsUltidocSidecarPath(path)
}

func sidecarPathsForSourceMove(sourcePath, destinationPath string) (sidecarSource, sidecarDestination string, ok bool) {
	if !shouldSyncUltidocSidecar(sourcePath) {
		return "", "", false
	}
	return nextcloud.SidecarPathForSource(sourcePath), nextcloud.SidecarPathForSource(destinationPath), true
}

func (s *Service) sidecarExists(ctx context.Context, userID, path string) bool {
	_, err := s.nc.FileRevision(ctx, userID, path)
	return err == nil
}

func (s *Service) syncUltidocSidecarCopy(
	ctx context.Context,
	userID, sourcePath, destinationPath string,
) error {
	sidecarSource, sidecarDestination, ok := sidecarPathsForSourceMove(sourcePath, destinationPath)
	if !ok || !s.sidecarExists(ctx, userID, sidecarSource) {
		return nil
	}

	if err := mapDriveError(s.nc.Copy(ctx, userID, sidecarSource, sidecarDestination)); err != nil {
		return err
	}

	return s.patchUltidocSidecarSourcePath(ctx, userID, sidecarDestination, destinationPath)
}

func (s *Service) patchUltidocSidecarSourcePath(
	ctx context.Context,
	userID, sidecarPath, newSourcePath string,
) error {
	body, _, err := s.nc.Download(ctx, userID, sidecarPath)
	if err != nil {
		return mapDriveError(err)
	}
	defer body.Close()

	raw, err := io.ReadAll(body)
	if err != nil {
		return err
	}

	var doc map[string]any
	if err := json.Unmarshal(raw, &doc); err != nil {
		return err
	}

	source, _ := doc["source"].(map[string]any)
	if source == nil {
		source = map[string]any{}
		doc["source"] = source
	}
	source["path"] = newSourcePath

	encoded, err := json.Marshal(doc)
	if err != nil {
		return err
	}

	if err := mapDriveError(s.nc.Upload(ctx, userID, sidecarPath, bytes.NewReader(encoded), "application/json")); err != nil {
		return err
	}
	return nil
}

func (s *Service) moveWithUltidocSidecar(ctx context.Context, userID, source, destination string) error {
	sidecarSource, sidecarDestination, ok := sidecarPathsForSourceMove(source, destination)
	hasSidecar := ok && s.sidecarExists(ctx, userID, sidecarSource)

	if hasSidecar {
		if err := mapDriveError(s.nc.Move(ctx, userID, sidecarSource, sidecarDestination)); err != nil {
			return err
		}
	}

	if err := mapDriveError(s.nc.Move(ctx, userID, source, destination)); err != nil {
		if hasSidecar {
			_ = s.nc.Move(ctx, userID, sidecarDestination, sidecarSource)
		}
		return err
	}

	if hasSidecar {
		if err := s.patchUltidocSidecarSourcePath(ctx, userID, sidecarDestination, destination); err != nil {
			return errors.Join(err, mapDriveError(s.nc.Move(ctx, userID, destination, source)))
		}
	}
	return nil
}
