package kb

import (
	"context"
	"errors"
	"sort"
	"strings"

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

func serviceGrantFromStoreWithRepositoryAccess(grant kb.KnowledgeBaseGrant, access []reposstore.ResourceRepositoryAccessGrant) KBPermissionGrant {
	profile := kb.NormalizeKnowledgeBaseRepositoryAccessProfile(grant.RepositoryAccessProfile)
	if grant.SubjectType == "agent" {
		profile = kb.KnowledgeBaseRepositoryAccessProfileRead
	}
	status, lastError := repositoryAccessStatus(profile, access)
	return KBPermissionGrant{ID: grant.ID, WorkspaceID: grant.WorkspaceID, KnowledgeBaseID: grant.KnowledgeBaseID, SubjectType: grant.SubjectType, SubjectID: grant.SubjectID, Role: grant.Role, RepositoryAccessProfile: profile, RepositoryAccessStatus: status, RepositoryAccessLastError: lastError, CreatedBy: grant.CreatedBy, UpdatedBy: grant.UpdatedBy, CreatedAt: grant.CreatedAt, UpdatedAt: grant.UpdatedAt}
}

func (s *KBService) knowledgeBaseRepositoryAccessByGrantID(ctx context.Context, workspaceID string, grants []kb.KnowledgeBaseGrant) (map[string][]reposstore.ResourceRepositoryAccessGrant, error) {
	out := make(map[string][]reposstore.ResourceRepositoryAccessGrant, len(grants))
	if s == nil || s.repositoryStore == nil || len(grants) == 0 {
		return out, nil
	}
	ids := make([]string, 0, len(grants))
	for _, grant := range grants {
		if strings.TrimSpace(grant.ID) != "" {
			ids = append(ids, grant.ID)
		}
	}
	rows, err := s.repositoryStore.ListAccessGrantsByIntentIDs(ctx, strings.TrimSpace(workspaceID), reposstore.AccessIntentTypeKBGrant, ids)
	if err != nil {
		return nil, err
	}
	for _, row := range rows {
		out[row.IntentID] = append(out[row.IntentID], row)
	}
	return out, nil
}

func (s *KBService) knowledgeBaseRepositoryAccessForGrant(ctx context.Context, workspaceID, grantID string) ([]reposstore.ResourceRepositoryAccessGrant, error) {
	if s == nil || s.repositoryStore == nil || strings.TrimSpace(grantID) == "" {
		return nil, nil
	}
	return s.repositoryStore.ListAccessGrantsForIntent(ctx, strings.TrimSpace(workspaceID), reposstore.AccessIntentTypeKBGrant, strings.TrimSpace(grantID))
}

func (s *KBService) knowledgeBaseSourceRepositoryAccessByGrantID(ctx context.Context, workspaceID, kbID string, grants []kb.KnowledgeBaseGrant, accessByGrantID map[string][]reposstore.ResourceRepositoryAccessGrant) (map[string][]KBPermissionRepositoryAccess, error) {
	out := make(map[string][]KBPermissionRepositoryAccess, len(grants))
	if s == nil || s.repositoryStore == nil || s.store == nil || len(grants) == 0 {
		return out, nil
	}
	sources, err := s.store.ListSources(ctx, strings.TrimSpace(workspaceID), strings.TrimSpace(kbID), 1, 10000)
	if err != nil {
		return nil, err
	}
	sourceNames := make(map[string]string, len(sources))
	sourceIDs := make([]string, 0, len(sources))
	for _, source := range sources {
		sourceIDs = append(sourceIDs, source.ID)
		sourceNames[source.ID] = source.Name
	}
	repos, err := s.repositoryStore.ListForResourceBatch(ctx, strings.TrimSpace(workspaceID), reposstore.ResourceTypeKBSource, sourceIDs)
	if err != nil {
		return nil, err
	}
	grantIDs := make([]string, 0, len(grants))
	for _, grant := range grants {
		grantIDs = append(grantIDs, grant.ID)
	}
	overrides, err := s.store.ListKnowledgeBaseSourceRepositoryAccessOverrides(ctx, strings.TrimSpace(workspaceID), strings.TrimSpace(kbID), grantIDs)
	if err != nil {
		return nil, err
	}
	overridesByGrantID := make(map[string]map[string]string, len(grants))
	for _, row := range overrides {
		if overridesByGrantID[row.GrantID] == nil {
			overridesByGrantID[row.GrantID] = make(map[string]string)
		}
		overridesByGrantID[row.GrantID][repositoryOverrideKey(row.SourceID, row.RepoRole)] = kb.NormalizeKnowledgeBaseRepositoryAccessProfile(row.RepositoryAccessProfile)
	}
	for _, grant := range grants {
		var eligibleRepos []reposstore.ResourceRepository
		inheritedProfile := kb.NormalizeKnowledgeBaseRepositoryAccessProfile(grant.RepositoryAccessProfile)
		if grant.SubjectType == "agent" {
			eligibleRepos = eligibleAgentRepositoryAccessRepos(repos)
			inheritedProfile = kb.KnowledgeBaseRepositoryAccessProfileRead
		} else {
			eligibleRepos = eligibleHumanRepositoryAccessRepos(repos)
		}
		out[grant.ID] = buildPermissionSourceRepositoryAccessRows(grant, eligibleRepos, sourceNames, overridesByGrantID[grant.ID], accessByGrantID[grant.ID], inheritedProfile)
	}
	return out, nil
}

func buildPermissionSourceRepositoryAccessRows(grant kb.KnowledgeBaseGrant, repos []reposstore.ResourceRepository, sourceNames map[string]string, overrides map[string]string, access []reposstore.ResourceRepositoryAccessGrant, inheritedProfile string) []KBPermissionRepositoryAccess {
	accessBySourceRepo := make(map[string]reposstore.ResourceRepositoryAccessGrant, len(access))
	for _, row := range access {
		accessBySourceRepo[repositoryOverrideKey(row.ResourceID, row.RepoRole)] = row
	}
	rows := make([]KBPermissionRepositoryAccess, 0, len(repos))
	for _, repo := range repos {
		key := repositoryOverrideKey(repo.ResourceID, repo.RepoRole)
		overrideProfile := ""
		effectiveProfile := inheritedProfile
		if grant.SubjectType == "user" {
			if value, ok := overrides[key]; ok {
				overrideProfile = value
				effectiveProfile = value
			}
		}
		row, hasAccessRow := accessBySourceRepo[key]
		status, lastError := sourceRepositoryAccessStatus(effectiveProfile, row, hasAccessRow)
		rows = append(rows, KBPermissionRepositoryAccess{
			SourceID:                         repo.ResourceID,
			SourceName:                       sourceNames[repo.ResourceID],
			RepoRole:                         repo.RepoRole,
			RepoFullName:                     repo.RepoFullName,
			InheritedRepositoryAccessProfile: inheritedProfile,
			OverrideRepositoryAccessProfile:  overrideProfile,
			EffectiveRepositoryAccessProfile: effectiveProfile,
			RepositoryAccessStatus:           status,
			RepositoryAccessLastError:        lastError,
		})
	}
	sort.SliceStable(rows, func(i, j int) bool {
		if rows[i].SourceName != rows[j].SourceName {
			return rows[i].SourceName < rows[j].SourceName
		}
		if rows[i].SourceID != rows[j].SourceID {
			return rows[i].SourceID < rows[j].SourceID
		}
		return rows[i].RepoRole < rows[j].RepoRole
	})
	return rows
}

func sourceRepositoryAccessStatus(profile string, row reposstore.ResourceRepositoryAccessGrant, hasRow bool) (string, string) {
	if kb.NormalizeKnowledgeBaseRepositoryAccessProfile(profile) == kb.KnowledgeBaseRepositoryAccessProfileNone {
		if hasRow && strings.TrimSpace(row.Status) == reposstore.AccessStatusBlocked {
			return kbRepositoryAccessStatusBlocked, strings.TrimSpace(row.LastError)
		}
		return kbRepositoryAccessStatusNotGranted, ""
	}
	if !hasRow || strings.TrimSpace(row.Status) == reposstore.AccessStatusRevoked {
		return kbRepositoryAccessStatusPending, ""
	}
	switch strings.TrimSpace(row.Status) {
	case reposstore.AccessStatusBlocked:
		return kbRepositoryAccessStatusBlocked, strings.TrimSpace(row.LastError)
	case reposstore.AccessStatusCurrent:
		return kbRepositoryAccessStatusCurrent, ""
	case reposstore.AccessStatusPending, "":
		return kbRepositoryAccessStatusPending, ""
	default:
		return kbRepositoryAccessStatusPending, ""
	}
}

func aggregateSourceRepositoryAccessStatus(rows []KBPermissionRepositoryAccess, fallbackStatus, fallbackLastError string) (string, string) {
	if len(rows) == 0 {
		if fallbackStatus == kbRepositoryAccessStatusBlocked {
			return fallbackStatus, fallbackLastError
		}
		return kbRepositoryAccessStatusNotGranted, ""
	}
	hasPending := false
	hasCurrent := false
	for _, row := range rows {
		switch strings.TrimSpace(row.RepositoryAccessStatus) {
		case kbRepositoryAccessStatusBlocked:
			return kbRepositoryAccessStatusBlocked, strings.TrimSpace(row.RepositoryAccessLastError)
		case kbRepositoryAccessStatusPending:
			hasPending = true
		case kbRepositoryAccessStatusCurrent:
			hasCurrent = true
		}
	}
	if hasPending {
		return kbRepositoryAccessStatusPending, ""
	}
	if hasCurrent {
		return kbRepositoryAccessStatusCurrent, ""
	}
	return kbRepositoryAccessStatusNotGranted, ""
}

func repositoryAccessStatus(profile string, access []reposstore.ResourceRepositoryAccessGrant) (string, string) {
	if kb.NormalizeKnowledgeBaseRepositoryAccessProfile(profile) == kb.KnowledgeBaseRepositoryAccessProfileNone {
		for _, grant := range access {
			if strings.TrimSpace(grant.Status) == reposstore.AccessStatusBlocked {
				return kbRepositoryAccessStatusBlocked, strings.TrimSpace(grant.LastError)
			}
		}
		return kbRepositoryAccessStatusNotGranted, ""
	}
	if len(access) == 0 {
		return kbRepositoryAccessStatusPending, ""
	}
	hasPending := false
	hasCurrent := false
	for _, grant := range access {
		switch strings.TrimSpace(grant.Status) {
		case reposstore.AccessStatusBlocked:
			return kbRepositoryAccessStatusBlocked, strings.TrimSpace(grant.LastError)
		case reposstore.AccessStatusPending, "":
			hasPending = true
		case reposstore.AccessStatusCurrent:
			hasCurrent = true
		}
	}
	if hasPending {
		return kbRepositoryAccessStatusPending, ""
	}
	if hasCurrent {
		return kbRepositoryAccessStatusCurrent, ""
	}
	return kbRepositoryAccessStatusPending, ""
}

func (s *KBService) decoratePermissionGrantSourceRepositoryAccess(ctx context.Context, base kb.KnowledgeBase, grant *KBPermissionGrant, access []reposstore.ResourceRepositoryAccessGrant) error {
	if grant == nil {
		return nil
	}
	storeGrant := kb.KnowledgeBaseGrant{
		ID:                      grant.ID,
		WorkspaceID:             grant.WorkspaceID,
		KnowledgeBaseID:         grant.KnowledgeBaseID,
		SubjectType:             grant.SubjectType,
		SubjectID:               grant.SubjectID,
		Role:                    grant.Role,
		RepositoryAccessProfile: grant.RepositoryAccessProfile,
		CreatedBy:               grant.CreatedBy,
		UpdatedBy:               grant.UpdatedBy,
		CreatedAt:               grant.CreatedAt,
		UpdatedAt:               grant.UpdatedAt,
	}
	sourceAccessByGrantID, err := s.knowledgeBaseSourceRepositoryAccessByGrantID(ctx, base.WorkspaceID, base.ID, []kb.KnowledgeBaseGrant{storeGrant}, map[string][]reposstore.ResourceRepositoryAccessGrant{grant.ID: access})
	if err != nil {
		return err
	}
	grant.SourceRepositoryAccess = sourceAccessByGrantID[grant.ID]
	grant.RepositoryAccessStatus, grant.RepositoryAccessLastError = aggregateSourceRepositoryAccessStatus(grant.SourceRepositoryAccess, grant.RepositoryAccessStatus, grant.RepositoryAccessLastError)
	return nil
}

func (s *KBService) decorateKnowledgeBaseAccess(ctx context.Context, base *kb.KnowledgeBase, subject string) {
	if base == nil {
		return
	}
	s.decorateKnowledgeBaseAccessWithWorkspaceAuthority(ctx, base, subject, s.canAdministerWorkspaceForRead(ctx, base.WorkspaceID, subject))
}

func (s *KBService) decorateKnowledgeBaseAccessWithWorkspaceAuthority(ctx context.Context, base *kb.KnowledgeBase, subject string, canAdministerWorkspace bool) {
	if base == nil {
		return
	}
	role := s.knowledgeBaseRoleWithWorkspaceAuthority(ctx, *base, subject, canAdministerWorkspace)
	base.Role = role
	base.CanManage = roleCanManage(role)
	base.RepositoryAccessProfile = kb.KnowledgeBaseRepositoryAccessProfileNone
	base.RepositoryAccessStatus = kbRepositoryAccessStatusNotGranted
	grant, err := s.store.GetKnowledgeBaseGrant(ctx, base.WorkspaceID, base.ID, "user", strings.TrimSpace(subject))
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) && role != "" && !canAdministerWorkspace {
			if strings.TrimSpace(base.PublicationState) == KBPublicationDraft {
				return
			}
			profile := normalizedKBDefaultRepositoryAccessProfile(base.DefaultRepositoryAccessProfile)
			base.RepositoryAccessProfile = profile
			base.RepositoryAccessStatus, _ = repositoryAccessStatus(profile, nil)
		}
		return
	}
	access, err := s.knowledgeBaseRepositoryAccessForGrant(ctx, base.WorkspaceID, grant.ID)
	if err != nil {
		return
	}
	converted := serviceGrantFromStoreWithRepositoryAccess(grant, access)
	base.RepositoryAccessProfile = converted.RepositoryAccessProfile
	base.RepositoryAccessStatus = converted.RepositoryAccessStatus
}

func (s *KBService) decorateKnowledgeBaseAccessList(ctx context.Context, bases []kb.KnowledgeBase, subject string) error {
	if len(bases) == 0 {
		return nil
	}
	return s.decorateKnowledgeBaseAccessListWithWorkspaceAuthority(ctx, bases, subject, s.canAdministerWorkspaceForRead(ctx, bases[0].WorkspaceID, subject))
}

func (s *KBService) decorateKnowledgeBaseAccessListWithWorkspaceAuthority(ctx context.Context, bases []kb.KnowledgeBase, subject string, canAdministerWorkspace bool) error {
	subject = strings.TrimSpace(subject)
	if len(bases) == 0 || subject == "" {
		return nil
	}
	workspaceID := strings.TrimSpace(bases[0].WorkspaceID)
	ids := make([]string, 0, len(bases))
	for i := range bases {
		bases[i].RepositoryAccessProfile = kb.KnowledgeBaseRepositoryAccessProfileNone
		bases[i].RepositoryAccessStatus = kbRepositoryAccessStatusNotGranted
		if strings.TrimSpace(bases[i].ID) == "" {
			continue
		}
		ids = append(ids, bases[i].ID)
		if strings.TrimSpace(bases[i].CreatedBy) == subject {
			bases[i].Role = "owner"
			bases[i].CanManage = true
		} else if canAdministerWorkspace {
			bases[i].Role = "manage"
			bases[i].CanManage = true
		}
	}
	if len(ids) == 0 {
		return nil
	}
	grants, err := s.store.ListKnowledgeBaseGrantsForSubject(ctx, workspaceID, "user", subject, ids)
	if err != nil {
		return err
	}
	grantByKBID := make(map[string]kb.KnowledgeBaseGrant, len(grants))
	for _, grant := range grants {
		grantByKBID[grant.KnowledgeBaseID] = grant
	}
	accessByGrantID, err := s.knowledgeBaseRepositoryAccessByGrantID(ctx, workspaceID, grants)
	if err != nil {
		return err
	}
	for i := range bases {
		grant, hasGrant := grantByKBID[bases[i].ID]
		if hasGrant {
			converted := serviceGrantFromStoreWithRepositoryAccess(grant, accessByGrantID[grant.ID])
			bases[i].RepositoryAccessProfile = converted.RepositoryAccessProfile
			bases[i].RepositoryAccessStatus = converted.RepositoryAccessStatus
			grantRole := strings.TrimSpace(grant.Role)
			if bases[i].Role == "" && grantRole != "none" && (strings.TrimSpace(bases[i].PublicationState) != KBPublicationDraft || roleCanEdit(grantRole)) {
				bases[i].Role = grantRole
			}
		}
		if bases[i].Role == "" && !hasGrant && strings.TrimSpace(bases[i].PublicationState) != KBPublicationDraft {
			bases[i].Role = normalizedKBDefaultWorkspaceMemberRole(bases[i].DefaultWorkspaceMemberRole)
		}
		if !hasGrant && bases[i].Role != "" && !canAdministerWorkspace && strings.TrimSpace(bases[i].PublicationState) != KBPublicationDraft {
			profile := normalizedKBDefaultRepositoryAccessProfile(bases[i].DefaultRepositoryAccessProfile)
			bases[i].RepositoryAccessProfile = profile
			bases[i].RepositoryAccessStatus, _ = repositoryAccessStatus(profile, nil)
		}
		bases[i].CanManage = roleCanManage(bases[i].Role)
	}
	return nil
}

func (s *KBService) blockedRepositoryAccessRevokeError(ctx context.Context, workspaceID, grantID string) error {
	rows, err := s.knowledgeBaseRepositoryAccessForGrant(ctx, strings.TrimSpace(workspaceID), strings.TrimSpace(grantID))
	if err != nil {
		return err
	}
	for _, row := range rows {
		if strings.TrimSpace(row.Status) != reposstore.AccessStatusBlocked {
			continue
		}
		message := strings.TrimSpace(row.LastError)
		if message == "" {
			message = "repository access revoke needs repair"
		}
		return conflict(message)
	}
	return nil
}
