//go:build integration

package integration_test

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log/slog"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"
	"time"

	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/config"
	gitcontract "bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/contracts/git"
	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/contracts/events"
	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/domain"
	gitpkg "bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/git"
	httpserver "bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/http"
	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/purge"
	redisclient "bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/redis"
	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/service"
)

func TestForceSyncPurgeAndFreshRun(t *testing.T) {
	store := testMongoStore(t)
	redis, redisCfg := testRedisClient(t, "")
	logger := slog.New(slog.NewTextHandler(io.Discard, nil))

	repoID := fmt.Sprintf("ad/force-sync-%d", time.Now().UnixNano())
	workspaceRoot := t.TempDir()
	cfg := config.Config{
		Git: config.GitConfig{WorkspacePath: workspaceRoot},
		Mongo: config.MongoConfig{
			URI:      os.Getenv("MONGO_URI"),
			Database: os.Getenv("MONGODB_DATABASE"),
		},
	}
	if cfg.Mongo.URI == "" {
		cfg.Mongo.URI = "mongodb://localhost:27018/adpilot_repo_sync"
	}
	if cfg.Mongo.Database == "" {
		cfg.Mongo.Database = "adpilot_repo_sync"
	}
	cfg.Pipeline = config.PipelineConfig{
		MongoURI:            cfg.Mongo.URI,
		RepoSyncDatabase:    cfg.Mongo.Database,
		CodeParserDatabase:  getenvDefault("CODE_PARSER_MONGO_DATABASE", "adpilot_code_parser"),
		CommitIntelDatabase: getenvDefault("COMMIT_INTEL_MONGO_DATABASE", "adpilot_commit_intel"),
		EmbeddingDatabase:   getenvDefault("EMBEDDING_MONGO_DATABASE", "adpilot_indexing"),
		DocsDatabase:        getenvDefault("DOCS_MONGO_DATABASE", "repo_docs"),
	}

	purger, err := purge.NewPipelinePurger(cfg.Pipeline)
	if err != nil {
		t.Fatalf("new purger: %v", err)
	}
	t.Cleanup(func() { _ = purger.Close(context.Background()) })

	publisher := redisclient.NewPublisher(redis, redisCfg, logger)
	git := &mockGitProvider{result: gitcontract.SyncResult{
		IsFirstSync: true,
		CloneURL:    "https://example.test/scm/ad/example.git",
		Snapshot: gitcontract.RepositorySnapshot{
			SnapshotID: "snap_force12345678",
			CommitSHA:  "abc123def456",
			Status:     "ready",
		},
		FileChanges: []gitcontract.FileChange{{
			Path:       "README.md",
			ChangeType: events.ChangeTypeAdded,
			Language:   "markdown",
			FileKind:   events.FileKindDocs,
		}},
	}}

	svc := service.NewSyncService(git, publisher, store, logger)
	svc.ConfigureForceSync(service.ForceSyncDeps{
		Config: cfg,
		Purger: purger,
	})

	ctx := context.Background()
	if err := store.UpsertRepository(ctx, domain.Repository{
		RepoID: repoID, CloneURL: "https://example.test/scm/ad/example.git", Status: domain.RepositoryStatusActive,
		UpdatedAt: time.Now().UTC(),
	}); err != nil {
		t.Fatalf("seed repository: %v", err)
	}

	cloneDir, err := gitpkg.WorkspaceDir(cfg, repoID)
	if err != nil {
		t.Fatalf("workspace dir: %v", err)
	}
	if err := os.MkdirAll(cloneDir, 0o755); err != nil {
		t.Fatalf("seed clone dir: %v", err)
	}

	accepted, err := svc.TriggerForceSync(repoID, "master", false)
	if err != nil {
		t.Fatalf("trigger force sync: %v", err)
	}
	if accepted.Mode != domain.IndexingRunModeFresh {
		t.Fatalf("mode=%q, want fresh", accepted.Mode)
	}
	if len(accepted.Purged) == 0 {
		t.Fatal("expected purge summary")
	}

	deadline := time.Now().Add(10 * time.Second)
	for time.Now().Before(deadline) {
		if svc.ActiveSyncCount() == 0 {
			break
		}
		time.Sleep(100 * time.Millisecond)
	}
	if svc.ActiveSyncCount() > 0 {
		t.Fatal("force sync did not finish")
	}

	if _, err := os.Stat(cloneDir); !os.IsNotExist(err) {
		t.Fatalf("expected clone dir removed before fresh sync, stat err=%v", err)
	}

	run, ok := svc.GetLatestIndexingRun(repoID)
	if !ok {
		t.Fatal("expected indexing run after force sync")
	}
	if run.Mode != domain.IndexingRunModeFresh {
		t.Fatalf("indexing run mode=%q, want fresh", run.Mode)
	}
	if run.ExpectedCodeFiles <= 0 {
		t.Fatalf("expected fresh sync to emit code files, got %d", run.ExpectedCodeFiles)
	}
}

func TestForceSyncHTTPEndpoint(t *testing.T) {
	store := testMongoStore(t)
	logger := slog.New(slog.NewTextHandler(io.Discard, nil))
	redis, redisCfg := testRedisClient(t, "")

	workspaceRoot := t.TempDir()
	cfg := config.Config{
		Git: config.GitConfig{WorkspacePath: workspaceRoot},
	}
	cfg.Mongo.URI = os.Getenv("MONGO_URI")
	cfg.Mongo.Database = os.Getenv("MONGODB_DATABASE")
	if cfg.Mongo.URI == "" {
		cfg.Mongo.URI = "mongodb://localhost:27018/adpilot_repo_sync"
	}
	if cfg.Mongo.Database == "" {
		cfg.Mongo.Database = "adpilot_repo_sync"
	}
	cfg.Pipeline = config.PipelineConfig{
		MongoURI:            cfg.Mongo.URI,
		RepoSyncDatabase:    cfg.Mongo.Database,
		CodeParserDatabase:  "adpilot_code_parser",
		CommitIntelDatabase: "adpilot_commit_intel",
		EmbeddingDatabase:   "adpilot_indexing",
		DocsDatabase:          "repo_docs",
	}

	purger, err := purge.NewPipelinePurger(cfg.Pipeline)
	if err != nil {
		t.Fatalf("new purger: %v", err)
	}
	t.Cleanup(func() { _ = purger.Close(context.Background()) })

	publisher := redisclient.NewPublisher(redis, redisCfg, logger)
	git := &mockGitProvider{result: gitcontract.SyncResult{
		IsFirstSync: true,
		Snapshot: gitcontract.RepositorySnapshot{
			SnapshotID: "snap_http12345678",
			CommitSHA:  "abc123def456",
		},
		FileChanges: []gitcontract.FileChange{{
			Path:       "main.go",
			ChangeType: events.ChangeTypeAdded,
			Language:   "go",
			FileKind:   events.FileKindCode,
		}},
	}}
	svc := service.NewSyncService(git, publisher, store, logger)
	svc.ConfigureForceSync(service.ForceSyncDeps{Config: cfg, Purger: purger})

	server := httpserver.New("repo-sync-service", func() bool { return true }, svc, svc, svc, svc, nil)
	req := httptest.NewRequest(http.MethodPost, "/admin/repos/ad%2Fexample-repo/force-sync", nil)
	rec := httptest.NewRecorder()
	server.Handler().ServeHTTP(rec, req)

	if rec.Code != http.StatusAccepted {
		t.Fatalf("expected 202, got %d body=%s", rec.Code, rec.Body.String())
	}

	var body map[string]any
	if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
		t.Fatalf("decode body: %v", err)
	}
	if body["mode"] != "fresh" || body["status"] != "accepted" {
		t.Fatalf("unexpected body: %+v", body)
	}
}

func getenvDefault(key, fallback string) string {
	if value := os.Getenv(key); value != "" {
		return value
	}
	return fallback
}
