package testkit

import (
	agentapi "github.com/tidbcloud/AgentTeam/internal/api/agent"
	"github.com/tidbcloud/AgentTeam/internal/archive"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/service/agentapproval"
	"github.com/tidbcloud/AgentTeam/internal/service/approvalapp"
	serviceaccountservice "github.com/tidbcloud/AgentTeam/internal/service/serviceaccount"
	"github.com/tidbcloud/AgentTeam/internal/service/team/roleproposal"
	"github.com/tidbcloud/AgentTeam/internal/store"
	"github.com/tidbcloud/AgentTeam/internal/store/admin"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/agentops"
	"github.com/tidbcloud/AgentTeam/internal/store/app"
	"github.com/tidbcloud/AgentTeam/internal/store/attachment"
	"github.com/tidbcloud/AgentTeam/internal/store/channel"
	"github.com/tidbcloud/AgentTeam/internal/store/identity"
	"github.com/tidbcloud/AgentTeam/internal/store/im"
	"github.com/tidbcloud/AgentTeam/internal/store/knowledgeqa"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
	"github.com/tidbcloud/AgentTeam/internal/store/onboarding"
	"github.com/tidbcloud/AgentTeam/internal/store/plan"
	"github.com/tidbcloud/AgentTeam/internal/store/privateagent"
	"github.com/tidbcloud/AgentTeam/internal/store/reaction"
	"github.com/tidbcloud/AgentTeam/internal/store/reporting"
	"github.com/tidbcloud/AgentTeam/internal/store/review"
	"github.com/tidbcloud/AgentTeam/internal/store/rewind"
	"github.com/tidbcloud/AgentTeam/internal/store/serviceaccount"
	"github.com/tidbcloud/AgentTeam/internal/store/team"
	"github.com/tidbcloud/AgentTeam/internal/store/workspace"
)

// StoreOptions mirrors the production store graph options at the API test
// fixture boundary. Keep it local to this package so tests configure the
// explicit domain store graph directly.
type StoreOptions = store.Options

// Store is the API integration-test fixture around the explicit store graph.
// It exposes named domain-store helpers only; it is not a root/all-store facade
// and does not embed a legacy root store or broad app/root composite port.
type Store struct {
	Graph *store.Graph
	DB    coredb.DB

	approvalAppProjector *approvalapp.Projector
	approvalSubjects     agentapi.ApprovalRuntime
}

// New constructs the explicit domain store graph at the API test fixture
// boundary with default graph options.
func New(db coredb.DB, archiveStorage archive.Storage, hotMessageRetentionDays int) *Store {
	return NewWithOptions(db, archiveStorage, hotMessageRetentionDays, StoreOptions{})
}

// NewWithOptions constructs the explicit domain store graph at the API test
// fixture boundary.
func NewWithOptions(db coredb.DB, archiveStorage archive.Storage, hotMessageRetentionDays int, options StoreOptions) *Store {
	root := &Store{
		Graph: store.NewWithOptions(db, archiveStorage, nil, hotMessageRetentionDays, options),
		DB:    db,
	}
	root.configureApprovalSubjects()
	return root
}

// Wrap adapts an explicit store graph into the testkit Store type.
func Wrap(graph *store.Graph) *Store {
	if graph == nil {
		return nil
	}
	return &Store{Graph: graph}
}

func graph(root *Store) *store.Graph {
	if root == nil {
		return nil
	}
	return root.Graph
}

func (root *Store) Admin() *admin.Store {
	if g := graph(root); g != nil {
		return g.Admin
	}
	return nil
}

func (root *Store) Agent() *agent.Service {
	if g := graph(root); g != nil {
		return g.Agent
	}
	return nil
}

func (root *Store) AgentOps() *agentops.Store {
	if g := graph(root); g != nil {
		return g.AgentOps
	}
	return nil
}

func (root *Store) PrivateAgents() *privateagent.Store {
	if g := graph(root); g != nil {
		return g.PrivateAgents
	}
	return nil
}

func (root *Store) App() *app.Store {
	if g := graph(root); g != nil {
		return g.App
	}
	return nil
}

func (root *Store) Attachment() *attachment.Store {
	if g := graph(root); g != nil {
		return g.Attachment
	}
	return nil
}

func (root *Store) Channel() *channel.Store {
	if g := graph(root); g != nil {
		return g.Channel
	}
	return nil
}

func (root *Store) Identity() *identity.Store {
	if g := graph(root); g != nil {
		return g.Identity
	}
	return nil
}

func (root *Store) IM() *im.Store {
	if g := graph(root); g != nil {
		return g.IM
	}
	return nil
}

func (root *Store) Message() *message.Store {
	if g := graph(root); g != nil {
		return g.Message
	}
	return nil
}

func (root *Store) Onboarding() *onboarding.Store {
	if g := graph(root); g != nil {
		return g.Onboarding
	}
	return nil
}

func (root *Store) KnowledgeQA() *knowledgeqa.Store {
	if g := graph(root); g != nil {
		return g.KnowledgeQA
	}
	return nil
}

func (root *Store) Plan() *plan.Store {
	if g := graph(root); g != nil {
		return g.Plan
	}
	return nil
}

func (root *Store) Reaction() *reaction.Store {
	if g := graph(root); g != nil {
		return g.Reaction
	}
	return nil
}

func (root *Store) Reporting() *reporting.Store {
	if g := graph(root); g != nil {
		return g.Reporting
	}
	return nil
}

func (root *Store) Rewind() *rewind.Store {
	if g := graph(root); g != nil {
		return g.Rewind
	}
	return nil
}

func (root *Store) Review() *review.Store {
	if g := graph(root); g != nil {
		return g.Review
	}
	return nil
}

func (root *Store) ServiceAccount() *serviceaccount.Store {
	if g := graph(root); g != nil {
		return g.ServiceAccount
	}
	return nil
}

func (root *Store) Team() *team.Store {
	if g := graph(root); g != nil {
		return g.Team
	}
	return nil
}

func (root *Store) Workspace() *workspace.Store {
	if g := graph(root); g != nil {
		return g.Workspace
	}
	return nil
}

func (root *Store) AgentStorePortsDeps() agentapi.StorePortsDeps {
	deps := agentapi.StorePortsDeps{
		Agents:           root.Agent(),
		App:              root.App(),
		Channel:          root.Channel(),
		Identity:         root.Identity(),
		Messages:         root.Message(),
		Reactions:        root.Reaction(),
		Rewind:           root.Rewind(),
		Review:           root.Review(),
		Teams:            root.Team(),
		ApprovalSubjects: root.approvalSubjects,
	}
	return deps
}

// ApprovalAppProjector returns the projector owned by this test composition
// root. Runtime fixtures reuse it so the process-local notification contract
// and subject presenter are registered exactly once, matching production.
func (root *Store) ApprovalAppProjector() *approvalapp.Projector {
	if root == nil {
		return nil
	}
	return root.approvalAppProjector
}

func (root *Store) configureApprovalSubjects() {
	if root == nil || root.Graph == nil || root.DB == nil {
		return
	}
	projector := approvalapp.NewProjector(
		root.App(), approvalapp.NewProvisioner(root.DB),
	)
	if err := projector.Register(); err != nil {
		panic("configure API test Approval projector: " + err.Error())
	}
	teamRoleProposals := roleproposal.New(root.DB)
	subjects := approvalapp.NewSubjectRegistry(root.Graph.Approval)
	if err := subjects.Register(approvalapp.NewServiceAccountCreateSubjectHandler(
		root.Graph.Approval,
		serviceaccountservice.New(root.DB),
		projector,
	)); err != nil {
		panic("configure API test service account Approval subject: " + err.Error())
	}
	if err := subjects.Register(approvalapp.NewTeamRoleProposalSubjectHandler(
		root.Graph.Approval,
		teamRoleProposals,
		projector,
	)); err != nil {
		panic("configure API test Team role proposal Approval subject: " + err.Error())
	}
	if err := subjects.Register(approvalapp.NewAgentManagementSubjectHandler(
		root.Graph.Approval,
		agentapproval.New(root.DB, root.Graph.Approval, root.Graph.AgentOps),
		projector,
	)); err != nil {
		panic("configure API test agent management Approval subject: " + err.Error())
	}
	subjects.Seal()
	root.approvalAppProjector = projector
	root.approvalSubjects = subjects
}
