package kb

import (
	"context"
	"strings"
	"time"

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

type KBOverview struct {
	KnowledgeBaseID  string
	Health           KBOverviewHealth
	Activity         []KBOverviewActivity
	Alerts           []KBOverviewAlert
	RecommendedPaths []KBOverviewRecommendedPath
	Sources          []KBOverviewSource
}

type KBOverviewHealth struct {
	Status         string
	TotalSources   int
	ReadySources   int
	AttentionCount int
	StaleSources   int
	FailedSources  int
	PendingSources int
}

type KBOverviewActivity struct {
	ID           string
	Kind         string
	ActorID      string
	Action       string
	ResourceType string
	ResourceID   string
	Status       string
	Summary      string
	CreatedAt    time.Time
}

type KBOverviewAlert struct {
	ID        string
	Severity  string
	Kind      string
	SourceID  string
	Message   string
	CreatedAt time.Time
}

type KBOverviewRecommendedPath struct {
	ID       string
	Label    string
	Reason   string
	Href     string
	SourceID string
}

type KBOverviewSource struct {
	SourceID                        string
	Name                            string
	SourceType                      string
	Status                          string
	SyncStatus                      string
	IndexStatus                     string
	AnalysisStatus                  string
	FreshnessStatus                 string
	RepoFullName                    string
	SourceRepositoryAccessProfile   string
	SourceRepositoryAccessStatus    string
	InsightsRepositoryAccessProfile string
	InsightsRepositoryAccessStatus  string
	Revision                        string
	UpdatedAt                       time.Time
	ImportProgress                  *kb.SourceImportProgress
}

type KBSourceDetail struct {
	KnowledgeBaseID  string
	Source           kb.Source
	Health           KBOverviewHealth
	Activity         []KBOverviewActivity
	Alerts           []KBOverviewAlert
	RecommendedPaths []KBOverviewRecommendedPath
	Repositories     KBSourceDetailRepositories
	RepositoryAccess map[string]KBPermissionRepositoryAccess
	CurrentRevision  *kb.SourceRevision
	AnalysisRuns     []kb.AnalysisRun
	Jobs             []kb.Job
}

type KBSourceDetailRepositories struct {
	Primary  *reposstore.ResourceRepository
	Mirror   *reposstore.ResourceRepository
	Insights *reposstore.ResourceRepository
}

func (s *KBService) GetKnowledgeBaseOverview(ctx context.Context, workspaceID, subject, kbID string) (KBOverview, error) {
	base, err := s.GetKnowledgeBase(ctx, workspaceID, subject, kbID)
	if err != nil {
		return KBOverview{}, err
	}
	sources, err := s.store.ListSources(ctx, base.WorkspaceID, base.ID, 1, 10000)
	if err != nil {
		return KBOverview{}, err
	}
	sources = s.withSourceMirrors(ctx, sources)
	sources, err = s.withSourceImportProgress(ctx, sources)
	if err != nil {
		return KBOverview{}, err
	}
	current := map[string]kb.SourceRevision{}
	for _, source := range sources {
		if revision, err := s.store.GetCurrentSourceRevision(ctx, base.WorkspaceID, base.ID, source.ID); err == nil {
			current[source.ID] = revision
		} else if err != gorm.ErrRecordNotFound {
			return KBOverview{}, err
		}
	}
	audit, err := s.store.ListAuditEvents(ctx, base.WorkspaceID, base.ID, 1, 10)
	if err != nil {
		return KBOverview{}, err
	}
	jobs, err := s.store.ListRecentJobsForKnowledgeBase(ctx, base.WorkspaceID, base.ID, kbImportProgressJobTypes(), 10)
	if err != nil {
		return KBOverview{}, err
	}
	repositoryAccessByKey, err := s.knowledgeBaseSourceRepositoryAccessForSubject(ctx, base, subject)
	if err != nil {
		return KBOverview{}, err
	}
	sources = decorateSourcesWithRepositoryAccess(sources, repositoryAccessByKey)
	overviewSources := make([]KBOverviewSource, 0, len(sources))
	alerts := make([]KBOverviewAlert, 0)
	for _, source := range sources {
		revision := current[source.ID]
		overviewSources = append(overviewSources, KBOverviewSource{
			SourceID:                        source.ID,
			Name:                            source.Name,
			SourceType:                      source.SourceType,
			Status:                          source.Status,
			SyncStatus:                      source.SyncStatus,
			IndexStatus:                     source.IndexStatus,
			AnalysisStatus:                  source.AnalysisStatus,
			FreshnessStatus:                 source.FreshnessStatus,
			SourceRepositoryAccessProfile:   source.SourceRepositoryAccessProfile,
			SourceRepositoryAccessStatus:    source.SourceRepositoryAccessStatus,
			InsightsRepositoryAccessProfile: source.InsightsRepositoryAccessProfile,
			InsightsRepositoryAccessStatus:  source.InsightsRepositoryAccessStatus,
			Revision:                        firstNonEmptyString(revision.PublishedWikiRevision, revision.InsightsCommitSHA, revision.SourceCommitSHA),
			UpdatedAt:                       source.UpdatedAt,
			ImportProgress:                  source.ImportProgress,
		})
		alerts = append(alerts, kbSourceAlerts(source, s.now())...)
	}
	return KBOverview{
		KnowledgeBaseID:  base.ID,
		Health:           kbHealthForSources(sources),
		Activity:         overviewActivities(audit, jobs, 10),
		Alerts:           alerts,
		RecommendedPaths: kbRecommendedPaths(base.ID, sources),
		Sources:          overviewSources,
	}, nil
}

func (s *KBService) GetKnowledgeBaseSourceDetail(ctx context.Context, workspaceID, subject, kbID, sourceID string) (KBSourceDetail, error) {
	base, err := s.GetKnowledgeBase(ctx, workspaceID, subject, kbID)
	if err != nil {
		return KBSourceDetail{}, err
	}
	source, err := s.store.GetSource(ctx, base.WorkspaceID, base.ID, sourceID)
	if err != nil {
		return KBSourceDetail{}, mapStoreNotFound(err)
	}
	sourcesWithMirror := s.withSourceMirrors(ctx, []kb.Source{source})
	sourcesWithProgress, err := s.withSourceImportProgress(ctx, sourcesWithMirror)
	if err != nil {
		return KBSourceDetail{}, err
	}
	source = sourcesWithProgress[0]
	var current *kb.SourceRevision
	if revision, err := s.store.GetCurrentSourceRevision(ctx, base.WorkspaceID, base.ID, source.ID); err == nil {
		current = &revision
	} else if err != gorm.ErrRecordNotFound {
		return KBSourceDetail{}, err
	}
	analysisRuns, err := s.store.ListAnalysisRuns(ctx, base.WorkspaceID, base.ID, source.ID, 1, 10)
	if err != nil {
		return KBSourceDetail{}, err
	}
	jobs, err := s.store.ListSourceJobs(ctx, base.WorkspaceID, base.ID, source.ID, "", 1, 10)
	if err != nil {
		return KBSourceDetail{}, err
	}
	audit, err := s.store.ListAuditEvents(ctx, base.WorkspaceID, base.ID, 1, 50)
	if err != nil {
		return KBSourceDetail{}, err
	}
	repositoryAccessByKey, err := s.knowledgeBaseSourceRepositoryAccessForSubject(ctx, base, subject)
	if err != nil {
		return KBSourceDetail{}, err
	}
	source = decorateSourcesWithRepositoryAccess([]kb.Source{source}, repositoryAccessByKey)[0]
	detail := KBSourceDetail{
		KnowledgeBaseID:  base.ID,
		Source:           source,
		Health:           kbHealthForSources([]kb.Source{source}),
		Activity:         sourceActivities(source.ID, audit, jobs, analysisRuns),
		Alerts:           kbSourceAlerts(source, s.now()),
		RecommendedPaths: kbRecommendedPaths(base.ID, []kb.Source{source}),
		RepositoryAccess: map[string]KBPermissionRepositoryAccess{},
		CurrentRevision:  current,
		AnalysisRuns:     analysisRuns,
		Jobs:             jobs,
	}
	if s.repositoryStore != nil {
		repos, err := s.repositoryStore.ListForResource(ctx, base.WorkspaceID, reposstore.ResourceTypeKBSource, source.ID)
		if err != nil {
			return KBSourceDetail{}, err
		}
		for i := range repos {
			repo := repos[i]
			switch repo.RepoRole {
			case reposstore.RepoRolePrimary:
				detail.Repositories.Primary = &repo
			case reposstore.RepoRoleMirror:
				detail.Repositories.Mirror = &repo
			case reposstore.RepoRoleInsights:
				detail.Repositories.Insights = &repo
			}
			if access, ok := repositoryAccessByKey[repositoryOverrideKey(repo.ResourceID, repo.RepoRole)]; ok {
				detail.RepositoryAccess[repo.RepoRole] = access
			}
		}
	}
	return detail, nil
}

func (s *KBService) knowledgeBaseSourceRepositoryAccessForSubject(ctx context.Context, base kb.KnowledgeBase, subject string) (map[string]KBPermissionRepositoryAccess, error) {
	out := map[string]KBPermissionRepositoryAccess{}
	self, err := s.selfKnowledgeBasePermissionGrant(ctx, base, subject)
	if err != nil {
		return nil, err
	}
	if self == nil {
		return out, nil
	}
	for _, row := range self.SourceRepositoryAccess {
		out[repositoryOverrideKey(row.SourceID, row.RepoRole)] = row
	}
	return out, nil
}

func decorateSourcesWithRepositoryAccess(sources []kb.Source, accessByKey map[string]KBPermissionRepositoryAccess) []kb.Source {
	for i := range sources {
		sourceRepoRole := reposstore.RepoRolePrimary
		if sources[i].Mirror != nil {
			sourceRepoRole = reposstore.RepoRoleMirror
		}
		if access, ok := accessByKey[repositoryOverrideKey(sources[i].ID, sourceRepoRole)]; ok {
			sources[i].SourceRepositoryAccessProfile = access.EffectiveRepositoryAccessProfile
			sources[i].SourceRepositoryAccessStatus = access.RepositoryAccessStatus
		}
		if access, ok := accessByKey[repositoryOverrideKey(sources[i].ID, reposstore.RepoRoleInsights)]; ok {
			sources[i].InsightsRepositoryAccessProfile = access.EffectiveRepositoryAccessProfile
			sources[i].InsightsRepositoryAccessStatus = access.RepositoryAccessStatus
		}
	}
	return sources
}

func (s *KBService) withSourceRepositoryAccess(ctx context.Context, base kb.KnowledgeBase, subject string, sources []kb.Source) ([]kb.Source, error) {
	accessByKey, err := s.knowledgeBaseSourceRepositoryAccessForSubject(ctx, base, subject)
	if err != nil {
		return nil, err
	}
	return decorateSourcesWithRepositoryAccess(sources, accessByKey), nil
}

func kbHealthForSources(sources []kb.Source) KBOverviewHealth {
	health := KBOverviewHealth{Status: "ready", TotalSources: len(sources)}
	for _, source := range sources {
		switch sourceStatusForHealth(source) {
		case "ready":
			health.ReadySources++
		case "failed":
			health.FailedSources++
			health.AttentionCount++
		case "stale":
			health.StaleSources++
			health.AttentionCount++
		default:
			health.PendingSources++
			health.AttentionCount++
		}
	}
	if health.TotalSources == 0 {
		health.Status = "empty"
	} else if health.FailedSources > 0 {
		health.Status = "failed"
	} else if health.StaleSources > 0 {
		health.Status = "stale"
	} else if health.PendingSources > 0 {
		health.Status = "pending"
	}
	return health
}

func sourceStatusForHealth(source kb.Source) string {
	for _, status := range []string{source.Status, source.SyncStatus, source.IndexStatus} {
		s := strings.TrimSpace(status)
		if s == "failed" || s == "error" {
			return "failed"
		}
		if s == "pending" || s == "queued" || s == "running" {
			return "pending"
		}
	}
	if !sourceReadyEnoughForOptionalWarnings(source) {
		s := strings.TrimSpace(source.AnalysisStatus)
		if s == "failed" || s == "error" {
			return "failed"
		}
		if s == "pending" || s == "queued" || s == "running" {
			return "pending"
		}
	}
	if strings.TrimSpace(source.FreshnessStatus) == "stale" {
		return "stale"
	}
	return "ready"
}

func kbSourceAlerts(source kb.Source, now time.Time) []KBOverviewAlert {
	alerts := make([]KBOverviewAlert, 0)
	if lastError := SanitizeKBProgressMessage(source.LastError); lastError != "" {
		alerts = append(alerts, KBOverviewAlert{ID: source.ID + ":last_error", Severity: "error", Kind: "source_error", SourceID: source.ID, Message: lastError, CreatedAt: source.UpdatedAt})
	}
	status := sourceStatusForHealth(source)
	if status == "failed" || status == "stale" || status == "pending" {
		alerts = append(alerts, KBOverviewAlert{ID: source.ID + ":" + status, Severity: alertSeverity(status), Kind: "source_health", SourceID: source.ID, Message: "source is " + status, CreatedAt: firstTime(source.UpdatedAt, now)})
	}
	return alerts
}

func kbRecommendedPaths(kbID string, sources []kb.Source) []KBOverviewRecommendedPath {
	paths := make([]KBOverviewRecommendedPath, 0)
	if len(sources) == 0 {
		paths = append(paths, KBOverviewRecommendedPath{ID: "add_source", Label: "Add a source", Reason: "Knowledge base has no sources", Href: "/kb/" + kbID + "/sources/new"})
		return paths
	}
	for _, source := range sources {
		status := sourceStatusForHealth(source)
		switch status {
		case "failed":
			paths = append(paths, KBOverviewRecommendedPath{ID: source.ID + ":inspect_error", Label: "Inspect source error", Reason: firstNonEmptyString(SanitizeKBProgressMessage(source.LastError), "Source needs attention"), Href: "/kb/" + kbID + "/sources/" + source.ID, SourceID: source.ID})
		case "stale":
			paths = append(paths, KBOverviewRecommendedPath{ID: source.ID + ":refresh", Label: "Refresh source", Reason: "Source revision is stale", Href: "/kb/" + kbID + "/sources/" + source.ID + "?action=refresh", SourceID: source.ID})
		case "pending":
			paths = append(paths, KBOverviewRecommendedPath{ID: source.ID + ":check_progress", Label: "Check indexing progress", Reason: "Source is not ready yet", Href: "/kb/" + kbID + "/sources/" + source.ID, SourceID: source.ID})
		}
	}
	if len(paths) == 0 {
		paths = append(paths, KBOverviewRecommendedPath{ID: "review_retrieval", Label: "Review retrieval coverage", Reason: "All sources are ready", Href: "/kb/" + kbID + "/overview"})
	}
	return paths
}

func sourceActivities(sourceID string, events []kb.AuditEvent, jobs []kb.Job, runs []kb.AnalysisRun) []KBOverviewActivity {
	out := make([]KBOverviewActivity, 0, len(events)+len(jobs)+len(runs))
	for _, event := range events {
		if event.ResourceID == sourceID || event.ResourceType == "knowledge_search" || event.ResourceType == "citation" {
			out = append(out, KBOverviewActivity{ID: event.ID, Kind: "audit", ActorID: event.ActorID, Action: event.Action, ResourceType: event.ResourceType, ResourceID: event.ResourceID, Status: "recorded", Summary: event.Summary, CreatedAt: event.CreatedAt})
		}
	}
	for _, job := range jobs {
		out = append(out, KBOverviewActivity{ID: job.ID, Kind: "job", Action: job.JobType, ResourceType: "job", ResourceID: job.ID, Status: job.Status, Summary: job.JobType, CreatedAt: job.CreatedAt})
	}
	for _, run := range runs {
		out = append(out, KBOverviewActivity{ID: run.ID, Kind: "analysis_run", Action: run.Trigger, ResourceType: "analysis_run", ResourceID: run.ID, Status: run.Status, Summary: run.Trigger, CreatedAt: run.CreatedAt})
	}
	return out
}

func alertSeverity(status string) string {
	switch status {
	case "failed":
		return "error"
	case "stale":
		return "warning"
	default:
		return "info"
	}
}

func firstTime(value time.Time, fallback time.Time) time.Time {
	if value.IsZero() {
		return fallback
	}
	return value
}
