package kb

import (
	"context"
	"errors"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/store/kb"
	"github.com/tidbcloud/AgentTeam/internal/store/resource/repos"
	"gorm.io/gorm"
)

const (
	kbRepositoryAccessStatusNotGranted = "not_granted"
	kbRepositoryAccessStatusPending    = "pending"
	kbRepositoryAccessStatusCurrent    = "current"
	kbRepositoryAccessStatusBlocked    = "blocked"
)

func (s *KBService) ListKnowledgeBasePermissions(ctx context.Context, workspaceID, subject, kbID string) (KBPermissionsSummary, error) {
	base, err := s.GetKnowledgeBase(ctx, workspaceID, subject, kbID)
	if err != nil {
		return KBPermissionsSummary{}, err
	}
	myRole := s.knowledgeBaseRole(ctx, base, subject)
	canManage := roleCanManage(myRole)
	if !canManage {
		self, err := s.selfKnowledgeBasePermissionGrant(ctx, base, subject)
		if err != nil {
			return KBPermissionsSummary{}, err
		}
		return KBPermissionsSummary{MyRole: myRole, CanManage: false, Self: self, Members: []KBPermissionGrant{}, Agents: []KBPermissionGrant{}}, nil
	}
	grants, err := s.store.ListKnowledgeBaseGrants(ctx, workspaceID, kbID)
	if err != nil {
		return KBPermissionsSummary{}, err
	}
	accessByGrantID, err := s.knowledgeBaseRepositoryAccessByGrantID(ctx, workspaceID, grants)
	if err != nil {
		return KBPermissionsSummary{}, err
	}
	sourceAccessByGrantID, err := s.knowledgeBaseSourceRepositoryAccessByGrantID(ctx, workspaceID, kbID, grants, accessByGrantID)
	if err != nil {
		return KBPermissionsSummary{}, err
	}
	out := KBPermissionsSummary{MyRole: myRole, CanManage: canManage, Members: []KBPermissionGrant{}, Agents: []KBPermissionGrant{}}
	for _, grant := range grants {
		converted := serviceGrantFromStoreWithRepositoryAccess(grant, accessByGrantID[grant.ID])
		converted.SourceRepositoryAccess = sourceAccessByGrantID[grant.ID]
		converted.RepositoryAccessStatus, converted.RepositoryAccessLastError = aggregateSourceRepositoryAccessStatus(converted.SourceRepositoryAccess, converted.RepositoryAccessStatus, converted.RepositoryAccessLastError)
		if grant.SubjectType == "user" && grant.SubjectID == strings.TrimSpace(subject) {
			self := converted
			out.Self = &self
		}
		if grant.SubjectType == "agent" {
			out.Agents = append(out.Agents, converted)
			continue
		}
		if grant.Role == "owner" && out.Owner == nil {
			owner := converted
			out.Owner = &owner
		}
		out.Members = append(out.Members, converted)
	}
	return out, nil
}

func (s *KBService) selfKnowledgeBasePermissionGrant(ctx context.Context, base kb.KnowledgeBase, subject string) (*KBPermissionGrant, error) {
	subject = strings.TrimSpace(subject)
	if subject == "" {
		return nil, nil
	}
	grant, err := s.store.GetKnowledgeBaseGrant(ctx, base.WorkspaceID, base.ID, "user", subject)
	if err == nil {
		access, err := s.knowledgeBaseRepositoryAccessForGrant(ctx, base.WorkspaceID, grant.ID)
		if err != nil {
			return nil, err
		}
		converted := serviceGrantFromStoreWithRepositoryAccess(grant, access)
		sourceAccessByGrantID, err := s.knowledgeBaseSourceRepositoryAccessByGrantID(ctx, base.WorkspaceID, base.ID, []kb.KnowledgeBaseGrant{grant}, map[string][]repos.ResourceRepositoryAccessGrant{grant.ID: access})
		if err != nil {
			return nil, err
		}
		converted.SourceRepositoryAccess = sourceAccessByGrantID[grant.ID]
		converted.RepositoryAccessStatus, converted.RepositoryAccessLastError = aggregateSourceRepositoryAccessStatus(converted.SourceRepositoryAccess, converted.RepositoryAccessStatus, converted.RepositoryAccessLastError)
		return &converted, nil
	}
	if !errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, err
	}
	role := normalizedKBDefaultWorkspaceMemberRole(base.DefaultWorkspaceMemberRole)
	if role == "" {
		return nil, nil
	}
	profile := normalizedKBDefaultRepositoryAccessProfile(base.DefaultRepositoryAccessProfile)
	syntheticGrant := kb.KnowledgeBaseGrant{
		ID:                      "inherited:" + base.ID + ":" + subject,
		WorkspaceID:             base.WorkspaceID,
		KnowledgeBaseID:         base.ID,
		SubjectType:             "user",
		SubjectID:               subject,
		Role:                    role,
		RepositoryAccessProfile: profile,
		CreatedBy:               base.CreatedBy,
		UpdatedBy:               base.UpdatedBy,
		CreatedAt:               base.CreatedAt,
		UpdatedAt:               base.UpdatedAt,
	}
	sourceAccessByGrantID, err := s.knowledgeBaseSourceRepositoryAccessByGrantID(ctx, base.WorkspaceID, base.ID, []kb.KnowledgeBaseGrant{syntheticGrant}, map[string][]repos.ResourceRepositoryAccessGrant{syntheticGrant.ID: []repos.ResourceRepositoryAccessGrant{}})
	if err != nil {
		return nil, err
	}
	converted := serviceGrantFromStoreWithRepositoryAccess(syntheticGrant, nil)
	converted.SourceRepositoryAccess = sourceAccessByGrantID[syntheticGrant.ID]
	converted.RepositoryAccessStatus, converted.RepositoryAccessLastError = aggregateSourceRepositoryAccessStatus(converted.SourceRepositoryAccess, converted.RepositoryAccessStatus, converted.RepositoryAccessLastError)
	return &converted, nil
}

func (s *KBService) UpsertKnowledgeBasePermission(ctx context.Context, workspaceID, subject, kbID string, input KBPermissionGrantMutation) (KBPermissionGrant, error) {
	base, err := s.GetKnowledgeBase(ctx, workspaceID, subject, kbID)
	if err != nil {
		return KBPermissionGrant{}, err
	}
	if !s.canManageKnowledgeBase(ctx, base, subject) {
		return KBPermissionGrant{}, forbidden("knowledge base requires manage access")
	}
	subjectType := normalizeKBGrantSubjectType(input.SubjectType)
	subjectID := strings.TrimSpace(input.SubjectID)
	grantID := strings.TrimSpace(input.GrantID)
	var existing kb.KnowledgeBaseGrant
	hasExisting := false
	if grantID != "" {
		existing, err = s.store.GetKnowledgeBaseGrantByID(ctx, workspaceID, kbID, grantID)
		if err != nil {
			return KBPermissionGrant{}, mapStoreNotFound(err)
		}
		hasExisting = true
		subjectType = existing.SubjectType
		subjectID = existing.SubjectID
	}
	roleInput := input.Role
	if hasExisting && !input.RoleSet && strings.TrimSpace(roleInput) == "" {
		roleInput = existing.Role
	}
	role := normalizeKBGrantRole(roleInput, subjectType)
	if subjectType == "" {
		return KBPermissionGrant{}, invalidArgument("permission subject type must be user or agent")
	}
	if subjectID == "" {
		return KBPermissionGrant{}, invalidArgument("permission subject id is required")
	}
	if subjectType == "agent" {
		if err := s.requireOwnedAgent(ctx, workspaceID, subject, subjectID); err != nil {
			return KBPermissionGrant{}, err
		}
	}
	if hasExisting && existing.SubjectType == "user" && existing.Role == "owner" && input.RoleSet && role != "owner" {
		return KBPermissionGrant{}, invalidArgument("knowledge base owner role cannot be changed")
	}
	previousRepositoryAccessProfile := kb.KnowledgeBaseRepositoryAccessProfileNone
	if hasExisting {
		previousRepositoryAccessProfile = kb.NormalizeKnowledgeBaseRepositoryAccessProfile(existing.RepositoryAccessProfile)
	}
	repositoryAccessProfile := normalizedKBDefaultRepositoryAccessProfile(base.DefaultRepositoryAccessProfile)
	if role == "none" {
		repositoryAccessProfile = kb.KnowledgeBaseRepositoryAccessProfileNone
	}
	if hasExisting {
		repositoryAccessProfile = kb.NormalizeKnowledgeBaseRepositoryAccessProfile(existing.RepositoryAccessProfile)
	}
	if subjectType == "agent" {
		repositoryAccessProfile = kb.KnowledgeBaseRepositoryAccessProfileNone
	}
	if input.RepositoryAccessProfile != nil {
		repositoryAccessProfile, err = normalizeKBRepositoryAccessProfile(*input.RepositoryAccessProfile)
		if err != nil {
			return KBPermissionGrant{}, err
		}
		if subjectType == "agent" && repositoryAccessProfile != kb.KnowledgeBaseRepositoryAccessProfileNone {
			return KBPermissionGrant{}, invalidArgument("repository access profile only applies to user grants")
		}
		if subjectType == "agent" {
			repositoryAccessProfile = kb.KnowledgeBaseRepositoryAccessProfileNone
		}
	}
	now := s.now()
	grant, err := s.store.UpsertKnowledgeBaseGrant(ctx, kb.KnowledgeBaseGrant{ID: newKBID("kbg"), WorkspaceID: strings.TrimSpace(workspaceID), KnowledgeBaseID: strings.TrimSpace(kbID), SubjectType: subjectType, SubjectID: subjectID, Role: role, RepositoryAccessProfile: repositoryAccessProfile, CreatedBy: strings.TrimSpace(subject), UpdatedBy: strings.TrimSpace(subject), CreatedAt: now, UpdatedAt: now})
	if err != nil {
		return KBPermissionGrant{}, err
	}
	if len(input.RepositoryOverrides) > 0 {
		if err := s.applyKnowledgeBasePermissionRepositoryOverrides(ctx, base, grant, subject, input.RepositoryOverrides); err != nil {
			return KBPermissionGrant{}, err
		}
	}
	if err := s.reconcileKnowledgeBaseGrantRepositoryAccess(ctx, base, grant, subject); err != nil {
		return KBPermissionGrant{}, err
	}
	access, err := s.knowledgeBaseRepositoryAccessForGrant(ctx, strings.TrimSpace(workspaceID), grant.ID)
	if err != nil {
		return KBPermissionGrant{}, err
	}
	converted := serviceGrantFromStoreWithRepositoryAccess(grant, access)
	if err := s.decoratePermissionGrantSourceRepositoryAccess(ctx, base, &converted, access); err != nil {
		return KBPermissionGrant{}, err
	}
	if err := s.auditRepositoryAccessProfileIntent(ctx, grant, subject, previousRepositoryAccessProfile, repositoryAccessProfile, converted.RepositoryAccessStatus, converted.RepositoryAccessLastError); err != nil {
		return KBPermissionGrant{}, err
	}
	return converted, nil
}

func (s *KBService) DeleteKnowledgeBasePermission(ctx context.Context, workspaceID, subject, kbID, grantID string) error {
	base, err := s.GetKnowledgeBase(ctx, workspaceID, subject, kbID)
	if err != nil {
		return err
	}
	if !s.canManageKnowledgeBase(ctx, base, subject) {
		return forbidden("knowledge base requires manage access")
	}
	grantID = strings.TrimSpace(grantID)
	if grantID == "" {
		return invalidArgument("permission grant id is required")
	}
	grant, err := s.store.GetKnowledgeBaseGrantByID(ctx, workspaceID, kbID, grantID)
	if err != nil {
		return mapStoreNotFound(err)
	}
	if grant.SubjectType == "user" && grant.Role == "owner" {
		return invalidArgument("knowledge base owner grant cannot be removed")
	}
	if err := s.reconcileDesiredRepositoryAccessRows(ctx, grant, nil, "", kb.NormalizeKnowledgeBaseRepositoryAccessProfile(grant.RepositoryAccessProfile), subject, resourceRepositoryActorForAccessRow(repos.ResourceRepositoryAccessGrant{SubjectType: grant.SubjectType, SubjectID: grant.SubjectID})); err != nil {
		return err
	}
	if err := s.blockedRepositoryAccessRevokeError(ctx, strings.TrimSpace(workspaceID), grant.ID); err != nil {
		return err
	}
	if err := s.auditRepositoryAccessProfileIntent(ctx, grant, subject, kb.NormalizeKnowledgeBaseRepositoryAccessProfile(grant.RepositoryAccessProfile), kb.KnowledgeBaseRepositoryAccessProfileNone, kbRepositoryAccessStatusNotGranted, ""); err != nil {
		return err
	}
	if err := s.store.DeleteKnowledgeBaseSourceRepositoryAccessOverridesForGrant(ctx, workspaceID, kbID, grantID); err != nil {
		return err
	}
	return mapStoreNotFound(s.store.DeleteKnowledgeBaseGrantByID(ctx, workspaceID, kbID, grantID))
}

func (s *KBService) ReconcileKnowledgeBasePermissionRepositoryAccess(ctx context.Context, workspaceID, subject, kbID, grantID string) (KBPermissionGrant, error) {
	base, err := s.GetKnowledgeBase(ctx, workspaceID, subject, kbID)
	if err != nil {
		return KBPermissionGrant{}, err
	}
	if !s.canManageKnowledgeBase(ctx, base, subject) {
		return KBPermissionGrant{}, forbidden("knowledge base requires manage access")
	}
	grant, err := s.store.GetKnowledgeBaseGrantByID(ctx, workspaceID, kbID, strings.TrimSpace(grantID))
	if err != nil {
		return KBPermissionGrant{}, mapStoreNotFound(err)
	}
	if err := s.reconcileKnowledgeBaseGrantRepositoryAccess(ctx, base, grant, subject); err != nil {
		return KBPermissionGrant{}, err
	}
	access, err := s.knowledgeBaseRepositoryAccessForGrant(ctx, strings.TrimSpace(workspaceID), grant.ID)
	if err != nil {
		return KBPermissionGrant{}, err
	}
	converted := serviceGrantFromStoreWithRepositoryAccess(grant, access)
	if err := s.decoratePermissionGrantSourceRepositoryAccess(ctx, base, &converted, access); err != nil {
		return KBPermissionGrant{}, err
	}
	if err := s.auditRepositoryAccessRepair(ctx, grant, subject, converted.RepositoryAccessStatus, converted.RepositoryAccessLastError); err != nil {
		return KBPermissionGrant{}, err
	}
	return converted, nil
}
