package kb

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"strings"

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

const maxWikiCorrelationIDBytes = 128

// ConfigureWikiGenerationApprovalGateway installs AgentTeam's generic Approval
// bridge. Embedded composition calls this exactly once before serving HTTP;
// the lock also keeps tests and shutdown-safe reads race free.
func (s *KBService) ConfigureWikiGenerationApprovalGateway(gateway kbgovernance.WikiGenerationApprovalGateway) {
	if s == nil {
		return
	}
	s.wikiApprovalMu.Lock()
	s.wikiApprovalGateway = gateway
	s.wikiApprovalMu.Unlock()
}

func (s *KBService) wikiGenerationApprovalGateway() kbgovernance.WikiGenerationApprovalGateway {
	if s == nil {
		return nil
	}
	s.wikiApprovalMu.RLock()
	defer s.wikiApprovalMu.RUnlock()
	return s.wikiApprovalGateway
}

// RequestWikiGenerationApproval is the Member path. MeshX freezes the Source
// revision first; AgentTeam derives all generic Approval identities and assignees.
func (s *KBService) RequestWikiGenerationApproval(ctx context.Context, workspaceID, subject, kbID, sourceID string, input KBWikiGenerationMutation) (KBSetupView, error) {
	gateway := s.wikiGenerationApprovalGateway()
	if gateway == nil {
		return KBSetupView{}, codedConflict("kb-wiki-approval-unavailable", "Wiki generation approvals are not configured", nil)
	}
	prepared, err := s.PrepareWikiGeneration(ctx, kbgovernance.PrepareWikiGenerationCommand{
		WorkspaceID: workspaceID, KnowledgeBaseID: kbID, SourceID: sourceID,
		SourceRevision: input.SourceRevision, Scope: input.Scope, Language: input.Language,
		Reason: input.Reason, RequesterID: subject, IdempotencyKey: input.IdempotencyKey,
		CorrelationID: input.IdempotencyKey,
	})
	if err != nil {
		return KBSetupView{}, err
	}
	if _, err := gateway.SubmitWikiGeneration(ctx, kbgovernance.SubmitWikiGenerationCommand{
		ActorPrincipalID: subject, Prepared: prepared,
	}); err != nil {
		return KBSetupView{}, mapWikiApprovalGatewayError(err)
	}
	return s.GetKnowledgeBaseSetup(ctx, workspaceID, subject, kbID)
}

// DecideWikiGenerationApproval applies one Admin review from the setup page.
func (s *KBService) DecideWikiGenerationApproval(ctx context.Context, workspaceID, subject, kbID, sourceID, approvalID string, expectedVersion int64, clientDecisionID, decision, reason string) (KBSetupView, error) {
	setup, err := s.requireSetupWikiApproval(ctx, workspaceID, subject, kbID, sourceID, approvalID)
	if err != nil {
		return KBSetupView{}, err
	}
	canAdminister, err := s.canAdministerWorkspace(ctx, workspaceID, subject)
	if err != nil {
		return KBSetupView{}, err
	}
	if !canAdminister {
		return KBSetupView{}, forbidden("Wiki approval decision requires workspace owner or admin access")
	}
	gateway := s.wikiGenerationApprovalGateway()
	if gateway == nil {
		return KBSetupView{}, codedConflict("kb-wiki-approval-unavailable", "Wiki generation approvals are not configured", nil)
	}
	if _, err := gateway.DecideWikiGeneration(ctx, kbgovernance.DecideWikiGenerationCommand{
		WorkspaceID: workspaceID, ApprovalID: approvalID, ActorPrincipalID: subject,
		ExpectedVersion: expectedVersion, ClientDecisionID: clientDecisionID,
		Decision: decision, Reason: reason, CorrelationID: setup.Session.ID,
	}); err != nil {
		return KBSetupView{}, mapWikiApprovalGatewayError(err)
	}
	return s.GetKnowledgeBaseSetup(ctx, workspaceID, subject, kbID)
}

// WithdrawWikiGenerationApproval cancels the applicant's pending request.
func (s *KBService) WithdrawWikiGenerationApproval(ctx context.Context, workspaceID, subject, kbID, sourceID, approvalID string, expectedVersion int64, clientRequestID string) (KBSetupView, error) {
	if _, err := s.requireSetupWikiApproval(ctx, workspaceID, subject, kbID, sourceID, approvalID); err != nil {
		return KBSetupView{}, err
	}
	gateway := s.wikiGenerationApprovalGateway()
	if gateway == nil {
		return KBSetupView{}, codedConflict("kb-wiki-approval-unavailable", "Wiki generation approvals are not configured", nil)
	}
	if _, err := gateway.WithdrawWikiGeneration(ctx, kbgovernance.WithdrawWikiGenerationCommand{
		WorkspaceID: workspaceID, ApprovalID: approvalID, ActorPrincipalID: subject,
		ExpectedVersion: expectedVersion, ClientRequestID: clientRequestID,
	}); err != nil {
		return KBSetupView{}, mapWikiApprovalGatewayError(err)
	}
	return s.GetKnowledgeBaseSetup(ctx, workspaceID, subject, kbID)
}

func (s *KBService) requireSetupWikiApproval(ctx context.Context, workspaceID, subject, kbID, sourceID, approvalID string) (KBSetupView, error) {
	setup, err := s.GetKnowledgeBaseSetup(ctx, workspaceID, subject, kbID)
	if err != nil {
		return KBSetupView{}, err
	}
	for _, source := range setup.Sources {
		if source.SourceID == strings.TrimSpace(sourceID) && source.WikiRequest != nil && source.WikiRequest.ApprovalID == strings.TrimSpace(approvalID) {
			return setup, nil
		}
	}
	return KBSetupView{}, notFound("Wiki generation approval was not found for this Source")
}

func mapWikiApprovalGatewayError(err error) error {
	var gatewayErr kbgovernance.GatewayError
	if !errors.As(err, &gatewayErr) {
		return err
	}
	switch gatewayErr.Kind {
	case kbgovernance.ErrorInvalidArgument:
		return codedInvalidArgument(gatewayErr.Code, gatewayErr.Message)
	case kbgovernance.ErrorForbidden:
		return codedForbidden(gatewayErr.Code, gatewayErr.Message)
	case kbgovernance.ErrorNotFound:
		return codedNotFound(gatewayErr.Code, gatewayErr.Message)
	case kbgovernance.ErrorConflict:
		return codedConflict(gatewayErr.Code, gatewayErr.Message, nil)
	default:
		return err
	}
}

// PrepareWikiGeneration validates a Member request and returns the exact
// immutable payload that AgentTeam's generic Approval authority must store.
func (s *KBService) PrepareWikiGeneration(ctx context.Context, command kbgovernance.PrepareWikiGenerationCommand) (kbgovernance.PreparedWikiGeneration, error) {
	if !s.WikiGenerationEnabled() {
		return kbgovernance.PreparedWikiGeneration{}, codedConflict("kb-wiki-generation-unavailable", "Wiki generation is not configured", nil)
	}
	base, err := s.GetKnowledgeBase(ctx, command.WorkspaceID, command.RequesterID, command.KnowledgeBaseID)
	if err != nil {
		return kbgovernance.PreparedWikiGeneration{}, err
	}
	if !roleCanEdit(s.knowledgeBaseRole(ctx, base, command.RequesterID)) {
		return kbgovernance.PreparedWikiGeneration{}, forbidden("Wiki generation approval requires knowledge base edit access")
	}
	admin, err := s.canAdministerWorkspace(ctx, command.WorkspaceID, command.RequesterID)
	if err != nil {
		return kbgovernance.PreparedWikiGeneration{}, err
	}
	if admin {
		return kbgovernance.PreparedWikiGeneration{}, codedConflict("kb-wiki-admin-direct-required", "Workspace owners and admins must use direct Wiki generation", nil)
	}
	if s.principalWorkspaceAccessVerifier == nil {
		return kbgovernance.PreparedWikiGeneration{}, invalidArgument("Wiki generation approval requires workspace membership verification")
	}
	active, err := s.principalWorkspaceAccessVerifier.HasWorkspaceAccess(ctx, command.WorkspaceID, command.RequesterID)
	if err != nil {
		return kbgovernance.PreparedWikiGeneration{}, err
	}
	if !active {
		return kbgovernance.PreparedWikiGeneration{}, forbidden("Wiki generation approval requires active workspace membership")
	}
	input, err := normalizeWikiGenerationMutation(KBWikiGenerationMutation{
		SourceRevision: command.SourceRevision, Scope: command.Scope,
		Language: command.Language, Reason: command.Reason,
		IdempotencyKey: command.IdempotencyKey,
	})
	if err != nil {
		return kbgovernance.PreparedWikiGeneration{}, err
	}
	correlationID := strings.TrimSpace(command.CorrelationID)
	if len(correlationID) > maxWikiCorrelationIDBytes {
		return kbgovernance.PreparedWikiGeneration{}, invalidArgument("Wiki generation correlation ID exceeds field limits")
	}
	source, err := s.store.GetSource(ctx, command.WorkspaceID, command.KnowledgeBaseID, command.SourceID)
	if err != nil {
		return kbgovernance.PreparedWikiGeneration{}, mapStoreNotFound(err)
	}
	native, err := s.kbSourceNativeActive(ctx, command.WorkspaceID, source.ID)
	if err != nil {
		return kbgovernance.PreparedWikiGeneration{}, err
	}
	if native {
		return kbgovernance.PreparedWikiGeneration{}, nativeKBSourceLegacyOperationConflict()
	}
	revision, err := s.store.GetCurrentSourceRevision(ctx, command.WorkspaceID, command.KnowledgeBaseID, source.ID)
	if err != nil {
		return kbgovernance.PreparedWikiGeneration{}, mapStoreNotFound(err)
	}
	if revision.ID != input.SourceRevision {
		return kbgovernance.PreparedWikiGeneration{}, codedConflict("kb-wiki-source-revision-stale", "The Source revision changed; refresh before requesting Wiki generation", nil)
	}
	if strings.TrimSpace(revision.PublishedWikiRevision) != "" {
		return kbgovernance.PreparedWikiGeneration{}, codedConflict("kb-wiki-already-ready", "The current Source revision already has a published Wiki", nil)
	}
	payload := kbgovernance.WikiGenerationPayload{
		SchemaVersion: kbgovernance.WikiGenerationPayloadSchema,
		WorkspaceID:   strings.TrimSpace(command.WorkspaceID), KnowledgeBaseID: base.ID,
		SourceID: source.ID, SourceRevision: revision.ID,
		SourceCommitSHA: revision.SourceCommitSHA, Scope: input.Scope,
		Language: input.Language, Reason: input.Reason,
		RequesterID: strings.TrimSpace(command.RequesterID), IdempotencyKey: input.IdempotencyKey,
	}
	payloadJSON, payloadDigest, err := kbgovernance.EncodePayload(payload)
	if err != nil {
		return kbgovernance.PreparedWikiGeneration{}, fmt.Errorf("encode Wiki generation Approval payload: %w", err)
	}
	preparedAt := command.Now
	if preparedAt.IsZero() {
		preparedAt = s.now()
	}
	return kbgovernance.PreparedWikiGeneration{
		Payload: payload, PayloadJSON: payloadJSON, PayloadDigest: payloadDigest,
		CorrelationID: correlationID, PreparedAt: preparedAt.UTC(),
	}, nil
}

// RecordWikiGenerationApproval binds the authoritative generic Approval to a
// durable MeshX request after revalidating the frozen payload.
func (s *KBService) RecordWikiGenerationApproval(ctx context.Context, command kbgovernance.RecordWikiApprovalCommand) (kbgovernance.WikiGenerationState, error) {
	if strings.TrimSpace(command.RequestID) == "" || strings.TrimSpace(command.ApprovalID) == "" || command.ApprovalVersion <= 0 {
		return kbgovernance.WikiGenerationState{}, invalidArgument("Wiki generation request and Approval identities are required")
	}
	existing, existingErr := s.store.GetGenerationRequestByApproval(ctx, command.Prepared.Payload.WorkspaceID, command.ApprovalID)
	if existingErr == nil {
		if existing.ID != strings.TrimSpace(command.RequestID) || existing.PayloadDigest != command.Prepared.PayloadDigest ||
			existing.RequesterID != command.Prepared.Payload.RequesterID || existing.IdempotencyKey != command.Prepared.Payload.IdempotencyKey {
			return kbgovernance.WikiGenerationState{}, codedConflict("kb-wiki-approval-replay-conflict", "The Wiki Approval is already bound to another request", nil)
		}
		return wikiGenerationState(existing), nil
	}
	if !errors.Is(existingErr, gorm.ErrRecordNotFound) {
		return kbgovernance.WikiGenerationState{}, existingErr
	}
	if command.ApprovalVersion != 1 {
		return kbgovernance.WikiGenerationState{}, codedConflict("kb-wiki-approval-record-missing", "A terminal Wiki Approval cannot create a missing domain request", nil)
	}
	fresh, err := s.PrepareWikiGeneration(ctx, kbgovernance.PrepareWikiGenerationCommand{
		WorkspaceID:     command.Prepared.Payload.WorkspaceID,
		KnowledgeBaseID: command.Prepared.Payload.KnowledgeBaseID,
		SourceID:        command.Prepared.Payload.SourceID,
		SourceRevision:  command.Prepared.Payload.SourceRevision,
		Scope:           command.Prepared.Payload.Scope, Language: command.Prepared.Payload.Language,
		Reason: command.Prepared.Payload.Reason, RequesterID: command.Prepared.Payload.RequesterID,
		IdempotencyKey: command.Prepared.Payload.IdempotencyKey,
		CorrelationID:  command.Prepared.CorrelationID,
	})
	if err != nil {
		return kbgovernance.WikiGenerationState{}, err
	}
	if fresh.PayloadDigest != command.Prepared.PayloadDigest || !bytes.Equal(fresh.PayloadJSON, command.Prepared.PayloadJSON) {
		return kbgovernance.WikiGenerationState{}, codedConflict("kb-wiki-approval-payload-mismatch", "The Wiki Approval payload changed before it was recorded", nil)
	}
	now := command.Now
	if now.IsZero() {
		now = s.now()
	}
	request := storekb.KnowledgeBaseGenerationRequest{
		ID: strings.TrimSpace(command.RequestID), WorkspaceID: fresh.Payload.WorkspaceID,
		KnowledgeBaseID: fresh.Payload.KnowledgeBaseID, SourceID: fresh.Payload.SourceID,
		Kind: storekb.GenerationKindWiki, SourceRevisionID: fresh.Payload.SourceRevision,
		SourceCommitSHA: fresh.Payload.SourceCommitSHA, Scope: fresh.Payload.Scope,
		Language: fresh.Payload.Language, Reason: fresh.Payload.Reason,
		RequesterID: fresh.Payload.RequesterID, ApprovalID: strings.TrimSpace(command.ApprovalID),
		ApprovalVersion: command.ApprovalVersion, Status: storekb.GenerationRequestStatusPendingApproval,
		IdempotencyKey: fresh.Payload.IdempotencyKey, PayloadDigest: fresh.PayloadDigest,
		Version: 1, CreatedAt: now, UpdatedAt: now,
	}
	stored, _, err := s.store.CreateWikiGeneration(ctx, storekb.CreateWikiGenerationParams{Request: request, Now: now})
	if err != nil {
		return kbgovernance.WikiGenerationState{}, mapWikiGenerationCreateError(err)
	}
	return wikiGenerationState(stored), nil
}

// ResolveWikiGenerationApproval applies an Approval decision idempotently.
// Workspace authority is rechecked for decisions; applicants may withdraw only
// their own pending request.
func (s *KBService) ResolveWikiGenerationApproval(ctx context.Context, command kbgovernance.ResolveWikiApprovalCommand) (kbgovernance.WikiGenerationState, error) {
	command.WorkspaceID = strings.TrimSpace(command.WorkspaceID)
	command.ApprovalID = strings.TrimSpace(command.ApprovalID)
	command.ActorPrincipalID = strings.TrimSpace(command.ActorPrincipalID)
	command.ClientDecisionID = strings.TrimSpace(command.ClientDecisionID)
	command.Decision = strings.TrimSpace(command.Decision)
	command.Reason = strings.TrimSpace(command.Reason)
	if command.WorkspaceID == "" || command.ApprovalID == "" || command.ApprovalVersion <= 0 || command.ActorPrincipalID == "" || command.ClientDecisionID == "" {
		return kbgovernance.WikiGenerationState{}, invalidArgument("Wiki Approval decision identities are required")
	}
	switch command.Decision {
	case kbgovernance.DecisionApproved, kbgovernance.DecisionWithdrawn:
	case kbgovernance.DecisionRejected:
		if command.Reason == "" {
			return kbgovernance.WikiGenerationState{}, invalidArgument("Wiki Approval rejection reason is required")
		}
	default:
		return kbgovernance.WikiGenerationState{}, invalidArgument("Wiki Approval decision is invalid")
	}
	request, err := s.store.GetGenerationRequestByApproval(ctx, command.WorkspaceID, command.ApprovalID)
	if err != nil {
		return kbgovernance.WikiGenerationState{}, mapStoreNotFound(err)
	}
	if request.DecisionID == "" {
		if command.Decision == kbgovernance.DecisionWithdrawn {
			if request.RequesterID != command.ActorPrincipalID {
				return kbgovernance.WikiGenerationState{}, forbidden("Only the Wiki request applicant may withdraw it")
			}
			if s.principalWorkspaceAccessVerifier == nil {
				return kbgovernance.WikiGenerationState{}, invalidArgument("Wiki Approval withdrawal requires workspace membership verification")
			}
			allowed, verifyErr := s.principalWorkspaceAccessVerifier.HasWorkspaceAccess(ctx, request.WorkspaceID, request.RequesterID)
			if verifyErr != nil {
				return kbgovernance.WikiGenerationState{}, verifyErr
			}
			if !allowed {
				return kbgovernance.WikiGenerationState{}, forbidden("Wiki request applicant is no longer an active workspace member")
			}
		} else {
			allowed, authorizeErr := s.canAdministerWorkspace(ctx, request.WorkspaceID, command.ActorPrincipalID)
			if authorizeErr != nil {
				return kbgovernance.WikiGenerationState{}, authorizeErr
			}
			if !allowed {
				return kbgovernance.WikiGenerationState{}, forbidden("Wiki approval decision requires workspace owner or admin access")
			}
		}
	}

	now := command.Now
	if now.IsZero() {
		now = s.now()
	}
	var job *storekb.Job
	if command.Decision == kbgovernance.DecisionApproved {
		native, authorityErr := s.kbSourceNativeActive(ctx, request.WorkspaceID, request.SourceID)
		if authorityErr != nil {
			return kbgovernance.WikiGenerationState{}, authorityErr
		}
		if native {
			return kbgovernance.WikiGenerationState{}, nativeKBSourceLegacyOperationConflict()
		}
		jobID := newKBID("job")
		payload, marshalErr := json.Marshal(wikiGenerationJobPayload{RequestID: request.ID, SourceRevision: request.SourceRevisionID})
		if marshalErr != nil {
			return kbgovernance.WikiGenerationState{}, fmt.Errorf("encode approved Wiki generation job: %w", marshalErr)
		}
		job = &storekb.Job{
			ID: jobID, WorkspaceID: request.WorkspaceID, KnowledgeBaseID: request.KnowledgeBaseID,
			SourceID: request.SourceID, JobType: KBJobTypeGenerateWiki, Status: KBJobStatusQueued,
			Priority: 100, Progress: string(payload), CreatedAt: now, UpdatedAt: now,
		}
	}
	audit := storekb.AuditEvent{
		ID: newKBID("audit"), WorkspaceID: request.WorkspaceID,
		KnowledgeBaseID: request.KnowledgeBaseID, ActorID: command.ActorPrincipalID,
		Action: "kb.wiki.approval.resolved", ResourceType: "source", ResourceID: request.SourceID,
		Summary: "Resolved Wiki generation approval", Details: kbJSONString(map[string]string{
			"approval_id": request.ApprovalID, "request_id": request.ID,
			"decision": command.Decision, "reason": command.Reason,
		}), CreatedAt: now,
	}
	resolved, _, err := s.store.ResolveWikiGenerationApproval(ctx, storekb.ResolveWikiGenerationApprovalParams{
		WorkspaceID: request.WorkspaceID, ApprovalID: request.ApprovalID, ApprovalVersion: command.ApprovalVersion,
		ActorID: command.ActorPrincipalID, ClientDecisionID: command.ClientDecisionID,
		Decision: command.Decision, Reason: command.Reason, Job: job,
		AuditEvent: audit, Now: now,
	})
	if err != nil {
		if errors.Is(err, storekb.ErrGenerationRequestReplayConflict) {
			return kbgovernance.WikiGenerationState{}, codedConflict("kb-wiki-approval-replay-conflict", "The Wiki Approval decision was already applied with another payload", err)
		}
		return kbgovernance.WikiGenerationState{}, err
	}
	return wikiGenerationState(resolved), nil
}

func mapWikiGenerationCreateError(err error) error {
	switch {
	case errors.Is(err, storekb.ErrGenerationRequestAlreadyActive):
		return codedConflict("kb-wiki-generation-active", "Wiki generation is already active for this Source", err)
	case errors.Is(err, storekb.ErrGenerationRequestReplayConflict):
		return codedConflict("kb-wiki-idempotency-conflict", "The Wiki request key is already bound to another payload", err)
	case errors.Is(err, storekb.ErrGenerationSourceRevisionChanged):
		return codedConflict("kb-wiki-source-revision-stale", "The Source revision changed; refresh before requesting Wiki generation", err)
	case errors.Is(err, storekb.ErrGenerationArtifactAlreadyReady):
		return codedConflict("kb-wiki-already-ready", "The current Source revision already has a published Wiki", err)
	default:
		return err
	}
}

func wikiGenerationState(request storekb.KnowledgeBaseGenerationRequest) kbgovernance.WikiGenerationState {
	return kbgovernance.WikiGenerationState{
		RequestID: request.ID, ApprovalID: request.ApprovalID, ApprovalVersion: request.ApprovalVersion,
		Status: request.Status, DomainVersion: request.Version,
		Stale: request.Status == storekb.GenerationRequestStatusStale,
	}
}

var _ kbgovernance.WikiGenerationAuthority = (*KBService)(nil)
