package kb

import (
	"context"
	"errors"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/kbgovernance"
	storekb "github.com/tidbcloud/AgentTeam/internal/store/kb"
	"gorm.io/gorm"
)

const (
	KBSetupModeCreateDraft  = "create_draft"
	KBSetupModeEditOfficial = storekb.SetupModeEditOfficial
	KBPublicationDraft      = "draft"
	KBPublicationOfficial   = "official"
	defaultKBSetupName      = "Untitled knowledge base"
)

var kbSetupSteps = map[string]struct{}{
	"basic": {}, "sources": {}, "wiki": {}, "dashboard": {}, "review": {},
}

type KBSetupSourceState struct {
	SourceID                  string
	SourceRevision            string
	SourceCommitSHA           string
	WikiState                 string
	WikiRequest               *storekb.KnowledgeBaseGenerationRequest
	CanReviewWikiGeneration   bool
	CanWithdrawWikiGeneration bool
	DashboardState            string
}

type KBSetupView struct {
	Session                  storekb.KnowledgeBaseSetupSession
	BasicInfo                KBSetupBasicInfo
	OfficialChangeFields     []string
	OfficialContentDigest    string
	StepSummary              map[string]string
	Sources                  []KBSetupSourceState
	PublicationRequest       *storekb.KnowledgeBasePublicationRequest
	PublicationApprovers     []kbgovernance.PublicationApprover
	CanRequestPublication    bool
	CanReviewPublication     bool
	CanWithdrawPublication   bool
	CanDirectWikiGeneration  bool
	CanRequestWikiGeneration bool
	CanDirectDashboardImport bool
	CanSaveOfficial          bool
}

type KBSetupBasicInfo struct {
	Name        string
	Description string
	GroupID     string
	TagIDs      []string
}

func (s *KBService) CreateKnowledgeBaseSetup(ctx context.Context, workspaceID, subject string, input KBMutation) (storekb.KnowledgeBase, KBSetupView, error) {
	if strings.TrimSpace(input.Name) == "" {
		input.Name = defaultKBSetupName
	}
	input.SetupMode = KBSetupModeCreateDraft
	base, err := s.CreateKnowledgeBase(ctx, workspaceID, subject, input)
	if err != nil {
		return storekb.KnowledgeBase{}, KBSetupView{}, err
	}
	setup, err := s.GetKnowledgeBaseSetup(ctx, workspaceID, subject, base.ID)
	return base, setup, err
}

func (s *KBService) GetKnowledgeBaseSetup(ctx context.Context, workspaceID, subject, kbID string) (KBSetupView, error) {
	base, err := s.GetKnowledgeBase(ctx, workspaceID, subject, kbID)
	if err != nil {
		return KBSetupView{}, err
	}
	if !roleCanEdit(s.knowledgeBaseRole(ctx, base, subject)) {
		return KBSetupView{}, forbidden("knowledge base setup requires edit access")
	}
	session, err := s.store.GetKnowledgeBaseSetupSession(ctx, workspaceID, kbID)
	if err != nil {
		return KBSetupView{}, mapStoreNotFound(err)
	}
	if err := s.authorizeKnowledgeBaseSetupSession(ctx, base, session, subject); err != nil {
		return KBSetupView{}, err
	}
	sources, err := s.loadKnowledgeBaseSetupSources(ctx, workspaceID, kbID)
	if err != nil {
		return KBSetupView{}, err
	}
	sourceIDs := setupSourceIDs(sources)
	revisions, err := s.store.ListCurrentSourceRevisions(ctx, workspaceID, sourceIDs)
	if err != nil {
		return KBSetupView{}, err
	}
	artifacts, err := s.store.ListKnowledgeBaseSetupSourceArtifacts(ctx, workspaceID, kbID, sourceIDs)
	if err != nil {
		return KBSetupView{}, err
	}
	requests, err := s.store.ListLatestGenerationRequestsBySource(ctx, workspaceID, kbID, storekb.GenerationKindWiki, sourceIDs)
	if err != nil {
		return KBSetupView{}, err
	}
	latestPublication, err := s.latestPublicationRequest(ctx, workspaceID, kbID)
	if err != nil {
		return KBSetupView{}, err
	}
	publicationReview := s.publicationApprovalReviewForRead(ctx, workspaceID, subject, latestPublication)
	revisionBySource := make(map[string]storekb.SourceRevision, len(revisions))
	for _, revision := range revisions {
		revisionBySource[revision.SourceID] = revision
	}
	dispositionBySourceKind := make(map[string]string, len(artifacts))
	for _, artifact := range artifacts {
		dispositionBySourceKind[artifact.SourceID+":"+artifact.Kind] = artifact.State
	}
	requestBySource := make(map[string]storekb.KnowledgeBaseGenerationRequest, len(requests))
	for _, request := range requests {
		requestBySource[request.SourceID] = request
	}

	states := make([]KBSetupSourceState, 0, len(sources))
	canAdminister := session.Mode == KBSetupModeEditOfficial
	if !canAdminister {
		canAdminister = s.canAdministerWorkspaceForRead(ctx, workspaceID, subject)
	}
	sourcesReady := len(sources) > 0
	wikiReady := len(sources) > 0
	dashboardReady := len(sources) > 0
	for _, source := range sources {
		if !setupSourceUsable(source) {
			sourcesReady = false
		}
		revision := revisionBySource[source.ID]
		var wikiRequest *storekb.KnowledgeBaseGenerationRequest
		request, hasWikiRequest := requestBySource[source.ID]
		if hasWikiRequest {
			wikiRequest = &request
		}
		wikiState := setupWikiState(revision, request, hasWikiRequest, dispositionBySourceKind[source.ID+":wiki"])
		dashboardState := setupDashboardState(revision)
		if dashboardState == "empty" && dispositionBySourceKind[source.ID+":dashboard"] == "skipped" {
			dashboardState = "skipped"
		}
		if wikiState != "ready" && wikiState != "skipped" {
			wikiReady = false
		}
		if dashboardState != "ready" && dashboardState != "skipped" {
			dashboardReady = false
		}
		states = append(states, KBSetupSourceState{
			SourceID: source.ID, SourceRevision: revision.ID,
			SourceCommitSHA: revision.SourceCommitSHA, WikiState: wikiState,
			WikiRequest: wikiRequest, DashboardState: dashboardState,
			CanReviewWikiGeneration:   canAdminister && wikiRequest != nil && wikiRequest.Status == storekb.GenerationRequestStatusPendingApproval,
			CanWithdrawWikiGeneration: wikiRequest != nil && wikiRequest.Status == storekb.GenerationRequestStatusPendingApproval && wikiRequest.RequesterID == subject,
		})
	}
	basicInfo, officialChanges, officialDigest, err := s.setupRevisionPresentation(ctx, base, session)
	if err != nil {
		return KBSetupView{}, err
	}
	name := strings.TrimSpace(basicInfo.Name)
	basicReady := name != "" && name != defaultKBSetupName && strings.TrimSpace(basicInfo.GroupID) != ""
	stepSummary := map[string]string{
		"basic": setupReadyState(basicReady), "sources": setupReadyState(sourcesReady),
		"wiki": setupReadyState(wikiReady), "dashboard": setupReadyState(dashboardReady),
		"review": setupReadyState(basicReady && sourcesReady && wikiReady && dashboardReady),
	}
	reviewReady := stepSummary["review"] == "ready"
	publicationPending := latestPublication != nil && latestPublication.Status == storekb.PublicationRequestStatusPendingApproval
	return KBSetupView{
		Session: session, BasicInfo: basicInfo, OfficialChangeFields: officialChanges, OfficialContentDigest: officialDigest,
		StepSummary: stepSummary, Sources: states,
		PublicationRequest:       latestPublication,
		PublicationApprovers:     publicationReview.Approvers,
		CanRequestPublication:    base.PublicationState == KBPublicationDraft && roleCanManage(s.knowledgeBaseRole(ctx, base, subject)) && reviewReady && !publicationPending && s.publicationGateway() != nil,
		CanReviewPublication:     canAdminister && publicationPending && publicationReview.CanDecide,
		CanWithdrawPublication:   publicationPending && latestPublication.RequesterID == subject,
		CanDirectWikiGeneration:  s.WikiGenerationEnabled() && canAdminister,
		CanRequestWikiGeneration: s.WikiGenerationEnabled() && !canAdminister && s.wikiGenerationApprovalGateway() != nil,
		CanDirectDashboardImport: canAdminister,
		CanSaveOfficial:          session.Mode == KBSetupModeEditOfficial && session.CompletedAt == nil && reviewReady && canAdminister,
	}, nil
}

func (s *KBService) loadKnowledgeBaseSetupSources(ctx context.Context, workspaceID, kbID string) ([]storekb.Source, error) {
	// Keep this explicit KB detail read bounded by the existing Source-page limit.
	sources, err := s.store.ListSources(ctx, workspaceID, kbID, 1, maxKBPerPage)
	if err != nil {
		return nil, err
	}
	mirrors, err := s.store.ListSourceMirrorsBySourceIDs(ctx, workspaceID, setupSourceIDs(sources))
	if err != nil {
		return nil, err
	}
	mirrorBySource := make(map[string]storekb.SourceMirror, len(mirrors))
	for _, mirror := range mirrors {
		mirrorBySource[mirror.SourceID] = mirror
	}
	for index := range sources {
		if mirror, ok := mirrorBySource[sources[index].ID]; ok {
			sources[index].Mirror = &mirror
		}
	}
	return s.withSourceImportProgress(ctx, sources)
}

func setupBasicInfoFromBase(base storekb.KnowledgeBase) KBSetupBasicInfo {
	result := KBSetupBasicInfo{Name: base.Name, Description: base.Description, GroupID: base.GroupID, TagIDs: make([]string, 0, len(base.Tags))}
	for _, tag := range base.Tags {
		result.TagIDs = append(result.TagIDs, tag.ID)
	}
	return result
}

func setupBasicInfoFromSnapshot(snapshot kbgovernance.PublicationSnapshot) KBSetupBasicInfo {
	return KBSetupBasicInfo{Name: snapshot.Name, Description: snapshot.Description, GroupID: snapshot.GroupID, TagIDs: append([]string(nil), snapshot.TagIDs...)}
}

func (s *KBService) authorizeKnowledgeBaseSetupSession(ctx context.Context, base storekb.KnowledgeBase, session storekb.KnowledgeBaseSetupSession, subject string) error {
	role := s.knowledgeBaseRole(ctx, base, subject)
	if session.Mode != KBSetupModeEditOfficial {
		if !roleCanEdit(role) {
			return forbidden("knowledge base setup requires edit access")
		}
		return nil
	}
	return s.requireOfficialEditor(ctx, base, subject)
}

func (s *KBService) publicationApprovalReviewForRead(ctx context.Context, workspaceID, subject string, request *storekb.KnowledgeBasePublicationRequest) kbgovernance.PublicationApprovalReview {
	gateway := s.publicationGateway()
	if request == nil || gateway == nil {
		return kbgovernance.PublicationApprovalReview{}
	}
	review, err := gateway.InspectPublication(ctx, kbgovernance.InspectPublicationApprovalCommand{
		WorkspaceID: workspaceID, ApprovalID: request.ApprovalID, ActorPrincipalID: subject,
	})
	if err != nil {
		return kbgovernance.PublicationApprovalReview{}
	}
	return review
}

func setupSourceIDs(sources []storekb.Source) []string {
	ids := make([]string, 0, len(sources))
	for _, source := range sources {
		ids = append(ids, source.ID)
	}
	return ids
}

func (s *KBService) latestPublicationRequest(ctx context.Context, workspaceID, kbID string) (*storekb.KnowledgeBasePublicationRequest, error) {
	request, err := s.store.GetLatestKnowledgeBasePublicationRequest(ctx, workspaceID, kbID)
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	return &request, nil
}

func setupWikiState(revision storekb.SourceRevision, request storekb.KnowledgeBaseGenerationRequest, hasRequest bool, disposition string) string {
	if strings.TrimSpace(revision.PublishedWikiRevision) != "" {
		return "ready"
	}
	state := "not_requested"
	if hasRequest {
		if request.SourceRevisionID != revision.ID || request.SourceCommitSHA != revision.SourceCommitSHA {
			state = storekb.GenerationRequestStatusStale
		} else {
			state = request.Status
		}
	}
	if disposition == "skipped" {
		switch state {
		case storekb.GenerationRequestStatusReady, storekb.GenerationRequestStatusQueued, storekb.GenerationRequestStatusRunning, storekb.GenerationRequestStatusPendingApproval:
			return state
		default:
			return "skipped"
		}
	}
	return state
}

func setupSourceUsable(source storekb.Source) bool {
	if source.ImportProgress != nil {
		switch source.ImportProgress.State {
		case "queued", "running", "failed":
			return false
		}
	}
	return strings.TrimSpace(source.Status) != "failed" && strings.TrimSpace(source.SyncStatus) != "failed"
}

func setupDashboardState(revision storekb.SourceRevision) string {
	if strings.TrimSpace(revision.UADashboardCommitSHA) == "" {
		return "empty"
	}
	_, state, err := classifyUADashboardManifest(revision)
	if err != nil {
		return "failed"
	}
	return state
}

func setupReadyState(ready bool) string {
	if ready {
		return "ready"
	}
	return "incomplete"
}

func (s *KBService) UpdateKnowledgeBaseSetupStep(ctx context.Context, workspaceID, subject, kbID, activeStep string, expectedVersion int64) (KBSetupView, error) {
	if _, ok := kbSetupSteps[strings.TrimSpace(activeStep)]; !ok || expectedVersion <= 0 {
		return KBSetupView{}, invalidArgument("valid setup step and expected version are required")
	}
	base, err := s.GetKnowledgeBase(ctx, workspaceID, subject, kbID)
	if err != nil {
		return KBSetupView{}, err
	}
	session, err := s.store.GetKnowledgeBaseSetupSession(ctx, workspaceID, kbID)
	if err != nil {
		return KBSetupView{}, mapStoreNotFound(err)
	}
	if session.CompletedAt != nil {
		return KBSetupView{}, codedConflict("kb-setup-completed", "Knowledge base setup is already complete", nil)
	}
	if err := s.authorizeKnowledgeBaseSetupSession(ctx, base, session, subject); err != nil {
		return KBSetupView{}, err
	}
	_, updated, err := s.store.UpdateKnowledgeBaseSetupStep(ctx, workspaceID, kbID, strings.TrimSpace(activeStep), subject, expectedVersion, s.now())
	if err != nil {
		return KBSetupView{}, err
	}
	if !updated {
		return KBSetupView{}, codedConflict("kb-setup-version-conflict", "Knowledge base setup changed on another device", nil)
	}
	return s.GetKnowledgeBaseSetup(ctx, workspaceID, subject, kbID)
}

func (s *KBService) SetKnowledgeBaseSetupSourceArtifact(ctx context.Context, workspaceID, subject, kbID, sourceID, kind, state string) (KBSetupView, error) {
	kind = strings.TrimSpace(kind)
	state = strings.TrimSpace(state)
	if (kind != "wiki" && kind != "dashboard") || (state != "skipped" && state != "not_requested") {
		return KBSetupView{}, invalidArgument("artifact kind and state are invalid")
	}
	base, err := s.GetKnowledgeBase(ctx, workspaceID, subject, kbID)
	if err != nil {
		return KBSetupView{}, err
	}
	session, err := s.store.GetKnowledgeBaseSetupSession(ctx, workspaceID, kbID)
	if err != nil {
		return KBSetupView{}, mapStoreNotFound(err)
	}
	if session.CompletedAt != nil {
		return KBSetupView{}, codedConflict("kb-setup-completed", "Knowledge base setup is already complete", nil)
	}
	if err := s.authorizeKnowledgeBaseSetupSession(ctx, base, session, subject); err != nil {
		return KBSetupView{}, err
	}
	if _, err := s.store.GetSource(ctx, workspaceID, kbID, sourceID); err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return KBSetupView{}, notFound("knowledge base source not found")
		}
		return KBSetupView{}, err
	}
	now := s.now()
	_, err = s.store.UpsertKnowledgeBaseSetupSourceArtifact(ctx, storekb.KnowledgeBaseSetupSourceArtifact{
		ID: newKBID("kbsa"), WorkspaceID: workspaceID, KnowledgeBaseID: kbID, SourceID: sourceID,
		Kind: kind, State: state, UpdatedBy: subject, CreatedAt: now, UpdatedAt: now,
	})
	if err != nil {
		return KBSetupView{}, err
	}
	return s.GetKnowledgeBaseSetup(ctx, workspaceID, subject, kbID)
}
