package channel

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/plan"
	"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) }

type AuthenticatedAgent struct {
	WorkspaceID string
	MemberID    string
}

type Channel struct {
	Type string
}

type HumanChannelAccess struct {
	Channel Channel
}

type Options struct {
	RequireWorkspaceAccess                  func(context.Context, string, string) (string, error)
	RequireChannelReadAccess                func(context.Context, string, string, string) (HumanChannelAccess, error)
	EnsureChannelScopeParticipantProjection func(context.Context, coredb.QueryExecer, string, string, string, time.Time) error
	MarkScopeParticipantProjectionRemoved   func(context.Context, coredb.QueryExecer, string, string, time.Time) error
	RemovePrivateEntriesForHostChannel      func(context.Context, coredb.QueryExecer, string, string, time.Time) error
	CheckWorkspaceQuotaInTx                 func(context.Context, coredb.Tx, string, plan.MetricKey, int64) error
}

type Store struct {
	db      coredb.DB
	options Options
}

func New(db coredb.DB, options Options) *Store {
	return &Store{db: db, options: options}
}

func (s *Store) Validate() error { return s.options.Validate() }

func (o Options) Validate() error {
	var missing []string
	if o.RequireWorkspaceAccess == nil {
		missing = append(missing, "RequireWorkspaceAccess")
	}
	if o.RequireChannelReadAccess == nil {
		missing = append(missing, "RequireChannelReadAccess")
	}
	if o.EnsureChannelScopeParticipantProjection == nil {
		missing = append(missing, "EnsureChannelScopeParticipantProjection")
	}
	if o.MarkScopeParticipantProjectionRemoved == nil {
		missing = append(missing, "MarkScopeParticipantProjectionRemoved")
	}
	if o.RemovePrivateEntriesForHostChannel == nil {
		missing = append(missing, "RemovePrivateEntriesForHostChannel")
	}
	if o.CheckWorkspaceQuotaInTx == nil {
		missing = append(missing, "CheckWorkspaceQuotaInTx")
	}
	if len(missing) > 0 {
		return fmt.Errorf("channel store: missing dependencies: %s", strings.Join(missing, ", "))
	}
	return nil
}

func (store *Store) requireWorkspaceAccess(ctx context.Context, userID string, workspaceID string) (string, error) {
	if store == nil || store.options.RequireWorkspaceAccess == nil {
		return "", fmt.Errorf("channel store workspace access dependency is not configured")
	}
	return store.options.RequireWorkspaceAccess(ctx, userID, workspaceID)
}

func (store *Store) requireChannelReadAccess(ctx context.Context, userID string, workspaceID string, channelID string) (HumanChannelAccess, error) {
	if store == nil || store.options.RequireChannelReadAccess == nil {
		return HumanChannelAccess{}, fmt.Errorf("channel store channel read access dependency is not configured")
	}
	return store.options.RequireChannelReadAccess(ctx, userID, workspaceID, channelID)
}

func (store *Store) ensureChannelScopeParticipantProjection(ctx context.Context, execer coredb.QueryExecer, channelID string, memberID string, deliveryPolicy string, now time.Time) error {
	if store == nil || store.options.EnsureChannelScopeParticipantProjection == nil {
		return fmt.Errorf("channel store scope participant projection dependency is not configured")
	}
	return store.options.EnsureChannelScopeParticipantProjection(ctx, execer, channelID, memberID, deliveryPolicy, now)
}

func (store *Store) markScopeParticipantProjectionRemoved(ctx context.Context, execer coredb.QueryExecer, channelID string, memberID string, now time.Time) error {
	if store == nil || store.options.MarkScopeParticipantProjectionRemoved == nil {
		return fmt.Errorf("channel store remove scope participant projection dependency is not configured")
	}
	return store.options.MarkScopeParticipantProjectionRemoved(ctx, execer, channelID, memberID, now)
}

func (store *Store) removePrivateEntriesForOwnerHostChannel(ctx context.Context, execer coredb.QueryExecer, ownerMemberID string, hostChannelID string, now time.Time) error {
	if store == nil || store.options.RemovePrivateEntriesForHostChannel == nil {
		return fmt.Errorf("channel store private-agent roster dependency is not configured")
	}
	return store.options.RemovePrivateEntriesForHostChannel(ctx, execer, ownerMemberID, hostChannelID, now)
}

func (store *Store) checkWorkspaceQuotaInTx(ctx context.Context, tx coredb.Tx, workspaceID string, metric plan.MetricKey, delta int64) error {
	if store == nil || store.options.CheckWorkspaceQuotaInTx == nil {
		return fmt.Errorf("channel store quota checker is not configured")
	}
	return store.options.CheckWorkspaceQuotaInTx(ctx, tx, workspaceID, metric, delta)
}
