package tenant

import (
	"context"
	"database/sql"
	"errors"
	"time"

	"github.com/mem9-ai/drive9/pkg/backend"
	"github.com/mem9-ai/drive9/pkg/meta"
)

// metaQuotaAdapter wraps *meta.Store to satisfy backend.MetaQuotaStore.
// This adapter bridges the meta package types to the backend view types,
// keeping the dependency direction: tenant → {backend, meta}.
type metaQuotaAdapter struct {
	s *meta.Store
}

// NewMetaQuotaAdapter wraps a *meta.Store to satisfy backend.MetaQuotaStore.
func NewMetaQuotaAdapter(s *meta.Store) backend.MetaQuotaStore {
	if s == nil {
		return nil
	}
	return &metaQuotaAdapter{s: s}
}

func (a *metaQuotaAdapter) GetQuotaConfig(ctx context.Context, tenantID string) (*backend.QuotaConfigView, error) {
	cfg, err := a.s.GetQuotaConfig(ctx, tenantID)
	if err != nil {
		return nil, err
	}
	return &backend.QuotaConfigView{
		TenantID:         cfg.TenantID,
		MaxStorageBytes:  cfg.MaxStorageBytes,
		MaxFileSizeBytes: cfg.MaxFileSizeBytes,
		MaxFileCount:     cfg.MaxFileCount,
		MaxMediaLLMFiles: cfg.MaxMediaLLMFiles,
		MaxVideoLLMFiles: cfg.MaxVideoLLMFiles,
		MaxMonthlyCostMC: cfg.MaxMonthlyCostMC,
	}, nil
}

func (a *metaQuotaAdapter) GetQuotaUsage(ctx context.Context, tenantID string) (*backend.QuotaUsageView, error) {
	u, err := a.s.GetQuotaUsage(ctx, tenantID)
	if err != nil {
		return nil, err
	}
	return &backend.QuotaUsageView{
		TenantID:         u.TenantID,
		StorageBytes:     u.StorageBytes,
		DBStorageBytes:   u.DBStorageBytes,
		S3StorageBytes:   u.S3StorageBytes,
		StorageTierReady: u.StorageTierReady,
		ReservedBytes:    u.ReservedBytes,
		FileCount:        u.FileCount,
		MediaFileCount:   u.MediaFileCount,
		VideoFileCount:   u.VideoFileCount,
	}, nil
}

func (a *metaQuotaAdapter) GetTierFileCounts(ctx context.Context, tenantID string) (*backend.TierFileCountView, error) {
	u, err := a.s.GetTierFileCounts(ctx, tenantID)
	if err != nil {
		return nil, err
	}
	return &backend.TierFileCountView{
		FileCount:   u.FileCount,
		DBFileCount: u.DBFileCount,
		S3FileCount: u.S3FileCount,
		TierReady:   u.TierReady,
	}, nil
}

func (a *metaQuotaAdapter) GetFileMetaForUpdateTx(tx *sql.Tx, tenantID, fileID string) (*backend.FileMetaView, error) {
	fm, err := a.s.GetFileMetaForUpdateTx(tx, tenantID, fileID)
	if err != nil {
		return nil, err
	}
	return &backend.FileMetaView{
		TenantID:    fm.TenantID,
		FileID:      fm.FileID,
		SizeBytes:   fm.SizeBytes,
		IsMedia:     fm.IsMedia,
		StorageType: backendStorageType(fm.StorageType),
		LayerOwned:  fm.LayerOwned,
	}, nil
}

func (a *metaQuotaAdapter) EnsureQuotaUsageRow(ctx context.Context, tenantID string) error {
	return a.s.EnsureQuotaUsageRow(ctx, tenantID)
}

func (a *metaQuotaAdapter) IncrStorageBytes(ctx context.Context, tenantID string, delta int64) error {
	return a.s.IncrStorageBytes(ctx, tenantID, delta)
}

func (a *metaQuotaAdapter) IncrStorageBytesTx(tx *sql.Tx, tenantID string, delta int64) error {
	return a.s.IncrStorageBytesTx(tx, tenantID, delta)
}

func (a *metaQuotaAdapter) IncrReservedBytes(ctx context.Context, tenantID string, delta int64) error {
	return a.s.IncrReservedBytes(ctx, tenantID, delta)
}

func (a *metaQuotaAdapter) IncrReservedBytesTx(tx *sql.Tx, tenantID string, delta int64) error {
	return a.s.IncrReservedBytesTx(tx, tenantID, delta)
}

func (a *metaQuotaAdapter) IncrFileCount(ctx context.Context, tenantID string, delta int64) error {
	return a.s.IncrFileCount(ctx, tenantID, delta)
}

func (a *metaQuotaAdapter) IncrFileCountTx(tx *sql.Tx, tenantID string, delta int64) error {
	return a.s.IncrFileCountTx(tx, tenantID, delta)
}

func (a *metaQuotaAdapter) IncrMediaFileCount(ctx context.Context, tenantID string, delta int64) error {
	return a.s.IncrMediaFileCount(ctx, tenantID, delta)
}

func (a *metaQuotaAdapter) IncrMediaFileCountTx(tx *sql.Tx, tenantID string, delta int64) error {
	return a.s.IncrMediaFileCountTx(tx, tenantID, delta)
}

func (a *metaQuotaAdapter) IncrVideoFileCount(ctx context.Context, tenantID string, delta int64) error {
	return a.s.IncrVideoFileCount(ctx, tenantID, delta)
}

func (a *metaQuotaAdapter) IncrVideoFileCountTx(tx *sql.Tx, tenantID string, delta int64) error {
	return a.s.IncrVideoFileCountTx(tx, tenantID, delta)
}

func (a *metaQuotaAdapter) TransferReservedToConfirmed(ctx context.Context, tenantID string, reservedDelta, storageDelta int64) error {
	return a.s.TransferReservedToConfirmed(ctx, tenantID, reservedDelta, storageDelta)
}

func (a *metaQuotaAdapter) TransferReservedToConfirmedTx(tx *sql.Tx, tenantID string, reservedDelta, storageDelta int64) error {
	return a.s.TransferReservedToConfirmedTx(tx, tenantID, reservedDelta, storageDelta)
}

func (a *metaQuotaAdapter) IncrQuotaUsageCountersTx(tx *sql.Tx, tenantID string, storageDelta, dbStorageDelta, s3StorageDelta, fileDelta, mediaDelta, reservedDelta int64) error {
	return a.s.IncrQuotaUsageCountersTx(tx, tenantID, storageDelta, dbStorageDelta, s3StorageDelta, fileDelta, mediaDelta, reservedDelta)
}

// AtomicReserveAndInsertUpload is the preferred single-transaction API for the
// upload-initiate path. See meta.Store.AtomicReserveAndInsertUpload for
// invariants. Translates meta sentinels to backend sentinels for the caller.
func (a *metaQuotaAdapter) AtomicReserveAndInsertUpload(ctx context.Context, r *backend.UploadReservationView) error {
	err := a.s.AtomicReserveAndInsertUpload(ctx, &meta.UploadReservation{
		TenantID:       r.TenantID,
		UploadID:       r.UploadID,
		ReservedBytes:  r.ReservedBytes,
		FileCountDelta: r.FileCountDelta,
		TargetPath:     r.TargetPath,
		Status:         r.Status,
		ExpiresAt:      r.ExpiresAt,
	})
	switch {
	case errors.Is(err, meta.ErrStorageQuotaExceeded):
		return backend.ErrStorageQuotaExceeded
	case errors.Is(err, meta.ErrFileCountQuotaExceeded):
		return backend.ErrFileCountQuotaExceeded
	case errors.Is(err, meta.ErrReservationAlreadyExists):
		return backend.ErrReservationAlreadyExists
	case errors.Is(err, meta.ErrQuotaReservationBusy):
		return backend.ErrQuotaReservationBusy
	}
	return err
}

func (a *metaQuotaAdapter) UpsertFileMeta(ctx context.Context, fm *backend.FileMetaView) error {
	return a.s.UpsertFileMeta(ctx, &meta.FileMeta{
		TenantID:    fm.TenantID,
		FileID:      fm.FileID,
		SizeBytes:   fm.SizeBytes,
		IsMedia:     fm.IsMedia,
		StorageType: metaStorageType(fm.StorageType),
		LayerOwned:  fm.LayerOwned,
	})
}

func (a *metaQuotaAdapter) GetFileMeta(ctx context.Context, tenantID, fileID string) (*backend.FileMetaView, error) {
	fm, err := a.s.GetFileMeta(ctx, tenantID, fileID)
	if err != nil {
		return nil, err
	}
	return &backend.FileMetaView{
		TenantID:    fm.TenantID,
		FileID:      fm.FileID,
		SizeBytes:   fm.SizeBytes,
		IsMedia:     fm.IsMedia,
		StorageType: backendStorageType(fm.StorageType),
		LayerOwned:  fm.LayerOwned,
	}, nil
}

func (a *metaQuotaAdapter) UpsertFileMetaTx(tx *sql.Tx, fm *backend.FileMetaView) error {
	return a.s.UpsertFileMetaTx(tx, &meta.FileMeta{
		TenantID:    fm.TenantID,
		FileID:      fm.FileID,
		SizeBytes:   fm.SizeBytes,
		IsMedia:     fm.IsMedia,
		StorageType: metaStorageType(fm.StorageType),
		LayerOwned:  fm.LayerOwned,
	})
}

func backendStorageType(value *meta.StorageType) *backend.StorageType {
	if value == nil {
		return nil
	}
	converted := backend.StorageType(*value)
	return &converted
}

func metaStorageType(value *backend.StorageType) *meta.StorageType {
	if value == nil {
		return nil
	}
	converted := meta.StorageType(*value)
	return &converted
}

func (a *metaQuotaAdapter) DeleteFileMeta(ctx context.Context, tenantID, fileID string) error {
	return a.s.DeleteFileMeta(ctx, tenantID, fileID)
}

func (a *metaQuotaAdapter) DeleteFileMetaTx(tx *sql.Tx, tenantID, fileID string) error {
	return a.s.DeleteFileMetaTx(tx, tenantID, fileID)
}

func (a *metaQuotaAdapter) DeleteFileMetaIfExistsTx(tx *sql.Tx, tenantID, fileID string) (bool, error) {
	return a.s.DeleteFileMetaIfExistsTx(tx, tenantID, fileID)
}

func (a *metaQuotaAdapter) InsertLayerStorageMetaIfAbsentTx(tx *sql.Tx, storage *backend.LayerStorageMetaView) (bool, error) {
	return a.s.InsertLayerStorageMetaIfAbsentTx(tx, &meta.LayerStorageMeta{
		TenantID: storage.TenantID, LayerID: storage.LayerID, PathHash: storage.PathHash,
		EntrySeq: storage.EntrySeq, SizeBytes: storage.SizeBytes, StorageType: meta.StorageType(storage.StorageType),
	})
}

func (a *metaQuotaAdapter) InsertUploadReservation(ctx context.Context, r *backend.UploadReservationView) error {
	return a.s.InsertUploadReservation(ctx, &meta.UploadReservation{
		TenantID:       r.TenantID,
		UploadID:       r.UploadID,
		ReservedBytes:  r.ReservedBytes,
		FileCountDelta: r.FileCountDelta,
		TargetPath:     r.TargetPath,
		Status:         r.Status,
		ExpiresAt:      r.ExpiresAt,
	})
}

func (a *metaQuotaAdapter) UpdateUploadReservationStatus(ctx context.Context, tenantID, uploadID, status string) error {
	return a.s.UpdateUploadReservationStatus(ctx, tenantID, uploadID, status)
}

func (a *metaQuotaAdapter) UpdateUploadReservationStatusTx(ctx context.Context, tx *sql.Tx, tenantID, uploadID, status string) error {
	return a.s.UpdateUploadReservationStatusTx(ctx, tx, tenantID, uploadID, status)
}

func (a *metaQuotaAdapter) AbortActiveReservationTx(ctx context.Context, tx *sql.Tx, tenantID, uploadID string) (bool, int64, int64, error) {
	return a.s.AbortActiveReservationTx(ctx, tx, tenantID, uploadID)
}

func (a *metaQuotaAdapter) SettleActiveReservationTx(ctx context.Context, tx *sql.Tx, tenantID, uploadID, status string) (bool, int64, error) {
	return a.s.SettleActiveReservationTx(ctx, tx, tenantID, uploadID, status)
}

func (a *metaQuotaAdapter) GetUploadReservation(ctx context.Context, tenantID, uploadID string) (*backend.UploadReservationView, error) {
	r, err := a.s.GetUploadReservation(ctx, tenantID, uploadID)
	if errors.Is(err, meta.ErrNotFound) {
		return nil, backend.ErrReservationNotFound
	}
	if err != nil {
		return nil, err
	}
	return &backend.UploadReservationView{
		TenantID:       r.TenantID,
		UploadID:       r.UploadID,
		ReservedBytes:  r.ReservedBytes,
		FileCountDelta: r.FileCountDelta,
		TargetPath:     r.TargetPath,
		Status:         r.Status,
		ExpiresAt:      r.ExpiresAt,
	}, nil
}

func (a *metaQuotaAdapter) InsertCentralLLMUsage(ctx context.Context, r *backend.LLMUsageView) error {
	return a.s.InsertCentralLLMUsage(ctx, &meta.LLMUsageRecord{
		TenantID:       r.TenantID,
		TaskType:       r.TaskType,
		TaskID:         r.TaskID,
		CostMillicents: r.CostMillicents,
		RawUnits:       r.RawUnits,
		RawUnitType:    r.RawUnitType,
	})
}

func (a *metaQuotaAdapter) InsertCentralLLMUsageTx(tx *sql.Tx, r *backend.LLMUsageView) error {
	return a.s.InsertCentralLLMUsageTx(tx, &meta.LLMUsageRecord{
		TenantID:       r.TenantID,
		TaskType:       r.TaskType,
		TaskID:         r.TaskID,
		CostMillicents: r.CostMillicents,
		RawUnits:       r.RawUnits,
		RawUnitType:    r.RawUnitType,
	})
}

func (a *metaQuotaAdapter) IncrMonthlyLLMCost(ctx context.Context, tenantID string, costMC int64) error {
	return a.s.IncrMonthlyLLMCost(ctx, tenantID, costMC)
}

func (a *metaQuotaAdapter) IncrMonthlyLLMCostTx(tx *sql.Tx, tenantID string, costMC int64) error {
	return a.s.IncrMonthlyLLMCostTx(tx, tenantID, costMC)
}

func (a *metaQuotaAdapter) MonthlyLLMCostMillicents(ctx context.Context, tenantID string) (int64, error) {
	return a.s.MonthlyLLMCostMillicents(ctx, tenantID)
}

func (a *metaQuotaAdapter) InsertMutationLog(ctx context.Context, entry *backend.MutationLogView) (int64, error) {
	return a.s.InsertMutationLog(ctx, &meta.MutationLogEntry{
		TenantID:     entry.TenantID,
		MutationType: entry.MutationType,
		MutationData: entry.MutationData,
	})
}

func (a *metaQuotaAdapter) ListPendingMutations(ctx context.Context, minAge time.Duration, limit int) ([]backend.MutationLogView, error) {
	entries, err := a.s.ListPendingMutations(ctx, minAge, limit)
	if err != nil {
		return nil, err
	}
	views := make([]backend.MutationLogView, len(entries))
	for i, e := range entries {
		views[i] = backend.MutationLogView{
			ID:             e.ID,
			TenantID:       e.TenantID,
			TiDBCloudOrgID: e.TiDBCloudOrgID,
			MutationType:   e.MutationType,
			MutationData:   e.MutationData,
			RetryCount:     e.RetryCount,
		}
	}
	return views, nil
}

func (a *metaQuotaAdapter) ObservePendingMutations(ctx context.Context) ([]backend.MutationBacklogView, error) {
	entries, err := a.s.ObservePendingMutations(ctx)
	if err != nil {
		return nil, err
	}
	views := make([]backend.MutationBacklogView, len(entries))
	for i, e := range entries {
		views[i] = backend.MutationBacklogView{
			TenantID:                e.TenantID,
			TiDBCloudOrgID:          e.TiDBCloudOrgID,
			PendingCount:            e.PendingCount,
			OldestPendingAgeSeconds: e.OldestPendingAgeSeconds,
		}
	}
	return views, nil
}

func (a *metaQuotaAdapter) HasPendingFileMutation(ctx context.Context, tenantID, fileID string) (bool, error) {
	return a.s.HasPendingFileMutation(ctx, tenantID, fileID)
}

func (a *metaQuotaAdapter) MarkMutationAppliedTx(tx *sql.Tx, id int64) error {
	return a.s.MarkMutationAppliedTx(tx, id)
}

func (a *metaQuotaAdapter) MarkMutationsAppliedTx(tx *sql.Tx, ids []int64) error {
	return a.s.MarkMutationsAppliedTx(tx, ids)
}

func (a *metaQuotaAdapter) IsMutationAlreadyAppliedError(err error) bool {
	return meta.IsMutationAlreadyAppliedError(err)
}

func (a *metaQuotaAdapter) IncrMutationRetry(ctx context.Context, id int64, maxRetries int) error {
	return a.s.IncrMutationRetry(ctx, id, maxRetries)
}

func (a *metaQuotaAdapter) InTx(ctx context.Context, fn func(tx *sql.Tx) error) error {
	return a.s.InTx(ctx, fn)
}

func (a *metaQuotaAdapter) PurgeAppliedMutations(ctx context.Context, cutoff time.Time, limit int) (int64, error) {
	return a.s.PurgeAppliedMutations(ctx, cutoff, limit)
}

// QuotaStoreIdentity returns the wrapped *meta.Store: every per-backend
// adapter over the same underlying store shares one dispatcher batch group.
func (a *metaQuotaAdapter) QuotaStoreIdentity() any { return a.s }

func (a *metaQuotaAdapter) EnqueueObjectGCCandidate(ctx context.Context, c *meta.ObjectGCCandidateInput) error {
	return a.s.EnqueueObjectGCCandidate(ctx, c)
}

func (a *metaQuotaAdapter) PrepareFailedWriteObjectGCCandidate(ctx context.Context, c *meta.ObjectGCCandidateInput) error {
	return a.s.PrepareFailedWriteObjectGCCandidate(ctx, c)
}

func (a *metaQuotaAdapter) BeginFailedWriteObjectGCCandidatePublish(ctx context.Context, namespaceID, storageRefHash, storageRef string) (bool, error) {
	return a.s.BeginFailedWriteObjectGCCandidatePublish(ctx, namespaceID, storageRefHash, storageRef)
}

func (a *metaQuotaAdapter) RetryFailedWriteObjectGCCandidate(ctx context.Context, namespaceID, storageRefHash, storageRef string, notBefore time.Time, lastError string) error {
	return a.s.RetryFailedWriteObjectGCCandidate(ctx, namespaceID, storageRefHash, storageRef, notBefore, lastError)
}

func (a *metaQuotaAdapter) DeleteFailedWriteObjectGCCandidate(ctx context.Context, namespaceID, storageRefHash, storageRef string) error {
	return a.s.DeleteFailedWriteObjectGCCandidate(ctx, namespaceID, storageRefHash, storageRef)
}

func (a *metaQuotaAdapter) DeletePendingObjectGCCandidate(ctx context.Context, namespaceID, storageRefHash, storageRef string, reason meta.ObjectGCCandidateReason) error {
	return a.s.DeletePendingObjectGCCandidate(ctx, namespaceID, storageRefHash, storageRef, reason)
}
