package attachment

import (
	"context"
	"sync"

	"github.com/tidbcloud/AgentTeam/internal/archive"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/objectstorage"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

var (
	ErrUnauthorized             = storeerr.ErrUnauthorized
	ErrForbidden                = storeerr.ErrForbidden
	ErrNotFound                 = storeerr.ErrNotFound
	ErrConflict                 = storeerr.ErrConflict
	ErrBadRequest               = storeerr.ErrBadRequest
	ErrGone                     = storeerr.ErrGone
	ErrSystemNotInitialized     = storeerr.ErrSystemNotInitialized
	ErrSystemAlreadyInitialized = storeerr.ErrSystemAlreadyInitialized
)

func badRequest(message string) error { return storeerr.BadRequest(message) }
func forbidden(message string) error  { return storeerr.Forbidden(message) }
func notFound(message string) error   { return storeerr.NotFound(message) }
func conflict(message string) error   { return storeerr.Conflict(message) }
func gone(message string) error       { return storeerr.Gone(message) }

var uploadedMediaObjectTrackers sync.Map

type Options struct {
	ResolveUploadScopeForUser               func(context.Context, coredb.Queryer, string, string, string) (string, string, error)
	ResolveUploadScopeForMember             func(context.Context, coredb.QueryRower, string, string, string) (string, error)
	AuthorizeAttachmentMessageReadForUser   func(context.Context, coredb.QueryExecer, string, string, string) error
	AuthorizeAttachmentMessageReadForMember func(context.Context, coredb.QueryExecer, string, string, string) error
	ThreadIDForChannelAccess                func(context.Context, coredb.QueryRower, string, string, string) (string, error)
	LoadHumanMemberID                       func(context.Context, coredb.QueryRower, string, string) (string, error)
	MediaPublicBaseURL                      string
	MediaObjectPrefix                       string
}

type sharedStore struct {
	archiveStorage     archive.Storage
	mediaObjectStorage objectstorage.Storage
	mediaPublicBaseURL string
	mediaObjectPrefix  string
	options            Options
}

type Store struct {
	db coredb.DB
	sharedStore
}

type TxStore struct {
	tx coredb.Tx
	sharedStore
	uploadedMediaObjects *uploadedMediaObjectTracker
}

func New(db coredb.DB, archiveStorage archive.Storage, mediaObjectStorage objectstorage.Storage, hotMessageRetentionDays int, options Options) *Store {
	return &Store{
		db: db,
		sharedStore: sharedStore{
			archiveStorage:     archiveStorage,
			mediaObjectStorage: mediaObjectStorage,
			mediaPublicBaseURL: options.MediaPublicBaseURL,
			mediaObjectPrefix:  options.MediaObjectPrefix,
			options:            options,
		},
	}
}

func (store *Store) InTx(tx coredb.Tx) TxStore {
	return TxStore{
		tx:                   tx,
		sharedStore:          store.sharedStore,
		uploadedMediaObjects: uploadedMediaObjectTrackerForTx(tx),
	}
}

func uploadedMediaObjectTrackerForTx(tx coredb.Tx) *uploadedMediaObjectTracker {
	if tx == nil {
		return nil
	}
	if tracker, ok := uploadedMediaObjectTrackers.Load(tx); ok {
		return tracker.(*uploadedMediaObjectTracker)
	}
	tracker := &uploadedMediaObjectTracker{}
	actual, _ := uploadedMediaObjectTrackers.LoadOrStore(tx, tracker)
	return actual.(*uploadedMediaObjectTracker)
}

func releaseUploadedMediaObjectTracker(tx coredb.Tx) {
	if tx == nil {
		return
	}
	uploadedMediaObjectTrackers.Delete(tx)
}

func (store sharedStore) resolveUploadScopeForUser(ctx context.Context, queryer coredb.Queryer, userID string, workspaceID string, scopeID string) (string, string, error) {
	if store.options.ResolveUploadScopeForUser == nil {
		return "", "", ErrNotFound
	}
	return store.options.ResolveUploadScopeForUser(ctx, queryer, userID, workspaceID, scopeID)
}

func (store sharedStore) resolveUploadScopeForMember(ctx context.Context, queryer coredb.QueryRower, memberID string, workspaceID string, scopeID string) (string, error) {
	if store.options.ResolveUploadScopeForMember != nil {
		return store.options.ResolveUploadScopeForMember(ctx, queryer, memberID, workspaceID, scopeID)
	}
	if _, err := store.threadIDForChannelAccess(ctx, queryer, memberID, workspaceID, scopeID); err != nil {
		return "", err
	}
	return scopeID, nil
}

func (store sharedStore) authorizeAttachmentMessageReadForUser(ctx context.Context, queryer coredb.QueryExecer, userID string, workspaceID string, messageID string) error {
	if store.options.AuthorizeAttachmentMessageReadForUser == nil {
		return ErrForbidden
	}
	return store.options.AuthorizeAttachmentMessageReadForUser(ctx, queryer, userID, workspaceID, messageID)
}

func (store sharedStore) authorizeAttachmentMessageReadForMember(ctx context.Context, queryer coredb.QueryExecer, memberID string, workspaceID string, messageID string) error {
	if store.options.AuthorizeAttachmentMessageReadForMember == nil {
		return ErrForbidden
	}
	return store.options.AuthorizeAttachmentMessageReadForMember(ctx, queryer, memberID, workspaceID, messageID)
}

func (store sharedStore) threadIDForChannelAccess(ctx context.Context, queryer coredb.QueryRower, memberID string, workspaceID string, channelID string) (string, error) {
	if store.options.ThreadIDForChannelAccess == nil {
		return "", ErrNotFound
	}
	return store.options.ThreadIDForChannelAccess(ctx, queryer, memberID, workspaceID, channelID)
}

func (store sharedStore) loadHumanMemberID(ctx context.Context, queryer coredb.QueryRower, userID string, workspaceID string) (string, error) {
	if store.options.LoadHumanMemberID == nil {
		return "", ErrNotFound
	}
	return store.options.LoadHumanMemberID(ctx, queryer, userID, workspaceID)
}
