package service_test

import (
	"context"
	"errors"
	"sync"

	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/domain"
	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/store"
)

var errStoreFailure = errors.New("store failure")

type mockMetadataStore struct {
	mu                 sync.Mutex
	repositories       map[string]domain.Repository
	snapshots          map[string]domain.Snapshot
	syncJobs           map[string]domain.SyncJob
	importJobs         map[string]domain.ImportJob
	orgRepoLinks       map[string]domain.OrgRepositoryLink
	indexingRuns       map[string]domain.IndexingRun
	snapshotFiles      []domain.SnapshotFile
	diagnostics        []domain.IndexingDiagnostic
	ready              bool
	failCreateJob      bool
	failUpsert         bool
	failSaveSnap       bool
	failCreateIndexing bool
	failInsertFiles    bool
}

func newMockMetadataStore() *mockMetadataStore {
	return &mockMetadataStore{
		repositories:  make(map[string]domain.Repository),
		snapshots:     make(map[string]domain.Snapshot),
		syncJobs:      make(map[string]domain.SyncJob),
		importJobs:    make(map[string]domain.ImportJob),
		orgRepoLinks:  make(map[string]domain.OrgRepositoryLink),
		indexingRuns:  make(map[string]domain.IndexingRun),
		snapshotFiles: nil,
		diagnostics:   nil,
		ready:         true,
	}
}

func (m *mockMetadataStore) Ready(context.Context) bool {
	return m.ready
}

func (m *mockMetadataStore) Close(context.Context) error {
	return nil
}

func (m *mockMetadataStore) EnsureIndexes(context.Context) error {
	return nil
}

func (m *mockMetadataStore) UpsertRepository(_ context.Context, repo domain.Repository) error {
	if m.failUpsert {
		return errStoreFailure
	}
	m.mu.Lock()
	defer m.mu.Unlock()
	m.repositories[repo.RepoID] = repo
	return nil
}

func (m *mockMetadataStore) GetRepository(_ context.Context, repoID string) (domain.Repository, bool, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	repo, ok := m.repositories[repoID]
	return repo, ok, nil
}

func (m *mockMetadataStore) ListRepositories(_ context.Context) ([]domain.Repository, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	repos := make([]domain.Repository, 0, len(m.repositories))
	for _, repo := range m.repositories {
		repos = append(repos, repo)
	}
	return repos, nil
}

func orgRepoLinkKey(orgID, repoID string) string {
	return orgID + "\x00" + repoID
}

func (m *mockMetadataStore) UpsertOrgRepositoryLink(_ context.Context, link domain.OrgRepositoryLink) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	key := orgRepoLinkKey(link.OrgID, link.RepoID)
	if _, exists := m.orgRepoLinks[key]; !exists {
		m.orgRepoLinks[key] = link
	}
	return nil
}

func (m *mockMetadataStore) ListRepoIDsByOrg(_ context.Context, orgID string) ([]string, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	var repoIDs []string
	for key, link := range m.orgRepoLinks {
		if link.OrgID != orgID {
			continue
		}
		repoIDs = append(repoIDs, link.RepoID)
		_ = key
	}
	return repoIDs, nil
}

func (m *mockMetadataStore) SaveSnapshot(_ context.Context, snap domain.Snapshot) error {
	if m.failSaveSnap {
		return errStoreFailure
	}
	m.mu.Lock()
	defer m.mu.Unlock()
	key := snap.RepoID + "/" + snap.SnapshotID
	m.snapshots[key] = snap
	return nil
}

func (m *mockMetadataStore) GetSnapshot(_ context.Context, repoID, snapshotID string) (domain.Snapshot, bool, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	snap, ok := m.snapshots[repoID+"/"+snapshotID]
	return snap, ok, nil
}

func (m *mockMetadataStore) GetLatestSnapshot(_ context.Context, repoID string) (domain.Snapshot, bool, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	var latest domain.Snapshot
	var found bool
	for _, snap := range m.snapshots {
		if snap.RepoID != repoID {
			continue
		}
		if !found || snap.CreatedAt.After(latest.CreatedAt) {
			latest = snap
			found = true
		}
	}
	return latest, found, nil
}

func (m *mockMetadataStore) CreateSyncJob(_ context.Context, job domain.SyncJob) error {
	if m.failCreateJob {
		return errStoreFailure
	}
	m.mu.Lock()
	defer m.mu.Unlock()
	m.syncJobs[job.JobID] = job
	return nil
}

func (m *mockMetadataStore) CreateImportJob(_ context.Context, job domain.ImportJob) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	if _, exists := m.importJobs[job.ImportJobID]; exists {
		return store.ErrDuplicateImportJob
	}
	m.importJobs[job.ImportJobID] = job
	return nil
}

func (m *mockMetadataStore) GetImportJob(_ context.Context, importJobID string) (domain.ImportJob, bool, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	job, ok := m.importJobs[importJobID]
	return job, ok, nil
}

func (m *mockMetadataStore) UpdateSyncJob(_ context.Context, job domain.SyncJob) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	if _, ok := m.syncJobs[job.JobID]; !ok {
		return errStoreFailure
	}
	m.syncJobs[job.JobID] = job
	return nil
}

func (m *mockMetadataStore) jobCount() int {
	m.mu.Lock()
	defer m.mu.Unlock()
	return len(m.syncJobs)
}

func (m *mockMetadataStore) snapshotCount() int {
	m.mu.Lock()
	defer m.mu.Unlock()
	return len(m.snapshots)
}

func (m *mockMetadataStore) repositoryCount() int {
	m.mu.Lock()
	defer m.mu.Unlock()
	return len(m.repositories)
}

func (m *mockMetadataStore) completedJobs() int {
	m.mu.Lock()
	defer m.mu.Unlock()
	count := 0
	for _, job := range m.syncJobs {
		if job.Status == domain.SyncStatusCompleted {
			count++
		}
	}
	return count
}

func (m *mockMetadataStore) failedJobs() int {
	m.mu.Lock()
	defer m.mu.Unlock()
	count := 0
	for _, job := range m.syncJobs {
		if job.Status == domain.SyncStatusFailed {
			count++
		}
	}
	return count
}

func (m *mockMetadataStore) CreateIndexingRun(_ context.Context, run domain.IndexingRun) error {
	if m.failCreateIndexing {
		return errStoreFailure
	}
	m.mu.Lock()
	defer m.mu.Unlock()
	m.indexingRuns[run.IndexingRunID] = run
	return nil
}

func (m *mockMetadataStore) UpdateIndexingRun(_ context.Context, run domain.IndexingRun) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	if _, ok := m.indexingRuns[run.IndexingRunID]; !ok {
		return errStoreFailure
	}
	m.indexingRuns[run.IndexingRunID] = run
	return nil
}

func (m *mockMetadataStore) GetIndexingRun(_ context.Context, indexingRunID string) (domain.IndexingRun, bool, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	run, ok := m.indexingRuns[indexingRunID]
	return run, ok, nil
}

func (m *mockMetadataStore) GetIndexingRunBySnapshot(_ context.Context, repoID, snapshotID string) (domain.IndexingRun, bool, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	for _, run := range m.indexingRuns {
		if run.RepoID == repoID && run.SnapshotID == snapshotID {
			return run, true, nil
		}
	}
	return domain.IndexingRun{}, false, nil
}

func (m *mockMetadataStore) GetLatestIndexingRunByRepo(_ context.Context, repoID string) (domain.IndexingRun, bool, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	var latest domain.IndexingRun
	var found bool
	for _, run := range m.indexingRuns {
		if run.RepoID != repoID {
			continue
		}
		if !found || run.StartedAt.After(latest.StartedAt) {
			latest = run
			found = true
		}
	}
	return latest, found, nil
}

func (m *mockMetadataStore) SaveIndexingDiagnostic(_ context.Context, diagnostic domain.IndexingDiagnostic) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.diagnostics = append(m.diagnostics, diagnostic)
	return nil
}

func (m *mockMetadataStore) ListIndexingDiagnostics(_ context.Context, repoID, snapshotID string, limit int) ([]domain.IndexingDiagnostic, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	var out []domain.IndexingDiagnostic
	for _, diagnostic := range m.diagnostics {
		if diagnostic.RepoID == repoID && diagnostic.SnapshotID == snapshotID {
			out = append(out, diagnostic)
		}
	}
	if limit > 0 && len(out) > limit {
		out = out[:limit]
	}
	return out, nil
}

func (m *mockMetadataStore) InsertSnapshotFiles(_ context.Context, files []domain.SnapshotFile) error {
	if m.failInsertFiles {
		return errStoreFailure
	}
	m.mu.Lock()
	defer m.mu.Unlock()
	m.snapshotFiles = append(m.snapshotFiles, files...)
	return nil
}

func (m *mockMetadataStore) ListSnapshotFiles(_ context.Context, repoID, snapshotID string) ([]domain.SnapshotFile, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	var out []domain.SnapshotFile
	for _, f := range m.snapshotFiles {
		if f.RepoID == repoID && f.SnapshotID == snapshotID {
			out = append(out, f)
		}
	}
	return out, nil
}

func (m *mockMetadataStore) indexingRunCount() int {
	m.mu.Lock()
	defer m.mu.Unlock()
	return len(m.indexingRuns)
}

func (m *mockMetadataStore) completedIndexingRuns() int {
	m.mu.Lock()
	defer m.mu.Unlock()
	count := 0
	for _, run := range m.indexingRuns {
		if run.Status == domain.IndexingRunStatusCompleted {
			count++
		}
	}
	return count
}

func (m *mockMetadataStore) snapshotFileCount() int {
	m.mu.Lock()
	defer m.mu.Unlock()
	return len(m.snapshotFiles)
}
