package attachment

import (
	"context"
	"errors"
	"fmt"
	"net/url"
	"path"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/objectstorage"
)

const (
	mediaObjectTypeAttachment = "attachments"
	mediaObjectTypeAvatar     = "avatars"
	mediaObjectTypeSticker    = "stickers"
)

type uploadedMediaObject struct {
	Driver    string
	ObjectKey string
}

type uploadedMediaObjectTracker struct {
	objects []uploadedMediaObject
}

func (store sharedStore) mediaStorageEnabled() bool {
	return store.mediaObjectStorage != nil
}

func (store sharedStore) mediaStorageDriver() string {
	if store.mediaObjectStorage == nil {
		return ""
	}
	return store.mediaObjectStorage.Driver()
}

func (txs TxStore) trackUploadedMediaObject(storageDriver string, objectKey string) {
	if txs.uploadedMediaObjects == nil {
		return
	}
	trimmedDriver := strings.TrimSpace(storageDriver)
	trimmedKey := strings.TrimSpace(objectKey)
	if trimmedDriver == "" || trimmedKey == "" {
		return
	}
	txs.uploadedMediaObjects.objects = append(txs.uploadedMediaObjects.objects, uploadedMediaObject{
		Driver:    trimmedDriver,
		ObjectKey: trimmedKey,
	})
}

func (txs TxStore) cleanupUploadedMediaObjects(ctx context.Context) {
	if txs.uploadedMediaObjects == nil || len(txs.uploadedMediaObjects.objects) == 0 {
		return
	}
	objects := txs.uploadedMediaObjects.objects
	txs.uploadedMediaObjects.objects = nil
	for _, uploaded := range objects {
		txs.deleteMediaObjectBestEffort(ctx, uploaded.Driver, uploaded.ObjectKey)
	}
}

func (txs TxStore) clearUploadedMediaObjects() {
	if txs.uploadedMediaObjects == nil {
		return
	}
	txs.uploadedMediaObjects.objects = nil
}

func (txs TxStore) releaseUploadedMediaObjectTracker() {
	txs.clearUploadedMediaObjects()
	releaseUploadedMediaObjectTracker(txs.tx)
}

func mediaObjectKey(objectType string, workspaceID string, objectID string) string {
	return strings.TrimSpace(objectType) + "/" + strings.TrimSpace(workspaceID) + "/" + strings.TrimSpace(objectID)
}

func (store sharedStore) putMediaObject(ctx context.Context, objectType string, workspaceID string, objectID string, contentType string, blob []byte) (string, string, error) {
	if !store.mediaStorageEnabled() {
		return "", "", nil
	}
	objectKey := mediaObjectKey(objectType, workspaceID, objectID)
	if err := store.mediaObjectStorage.Put(ctx, objectKey, contentType, blob); err != nil {
		return "", "", fmt.Errorf("put %s media object %s: %w", objectType, objectID, err)
	}
	return store.mediaStorageDriver(), objectKey, nil
}

func (store sharedStore) loadMediaObject(ctx context.Context, storageDriver string, objectKey string, fallback []byte) ([]byte, error) {
	trimmedDriver := strings.TrimSpace(storageDriver)
	trimmedKey := strings.TrimSpace(objectKey)
	if trimmedDriver == "" || trimmedKey == "" {
		return fallback, nil
	}
	if store.mediaObjectStorage == nil {
		if len(fallback) > 0 {
			return fallback, nil
		}
		return nil, fmt.Errorf("media object storage driver %q is configured in data but not enabled in process", trimmedDriver)
	}
	blob, err := store.mediaObjectStorage.Get(ctx, trimmedKey)
	if err == nil {
		return blob, nil
	}
	if len(fallback) > 0 && errors.Is(err, objectstorage.ErrObjectNotFound) {
		return fallback, nil
	}
	return nil, fmt.Errorf("load media object %s: %w", trimmedKey, err)
}

func (store sharedStore) deleteMediaObjectBestEffort(ctx context.Context, storageDriver string, objectKey string) {
	if store.mediaObjectStorage == nil {
		return
	}
	if strings.TrimSpace(storageDriver) == "" || strings.TrimSpace(objectKey) == "" {
		return
	}
	_ = store.mediaObjectStorage.Delete(ctx, objectKey)
}

func (store sharedStore) mediaObjectPublicURL(storageDriver string, objectKey string) string {
	if strings.TrimSpace(storageDriver) == "" || strings.TrimSpace(objectKey) == "" {
		return ""
	}
	baseURL := strings.TrimSpace(store.mediaPublicBaseURL)
	if baseURL == "" {
		return ""
	}
	parsedURL, err := url.Parse(baseURL)
	if err != nil {
		return ""
	}
	joinedPath := path.Join("/", strings.Trim(strings.TrimSpace(store.mediaObjectPrefix), "/"), strings.Trim(strings.TrimSpace(objectKey), "/"))
	parsedURL.Path = joinedPath
	parsedURL.RawPath = ""
	return parsedURL.String()
}
