package httpserver_test

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	apicontract "bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/contracts/api"
	httpserver "bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/http"
	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/service"
)

type mockSyncTrigger struct {
	repoID string
	ref    string
	jobID  string
	err    error
}

func (m *mockSyncTrigger) TriggerSync(repoID, ref string) (string, error) {
	m.repoID = repoID
	m.ref = ref
	if m.err != nil {
		return "", m.err
	}
	if m.jobID == "" {
		return "job_test12345678", nil
	}
	return m.jobID, nil
}

type mockForceSyncTrigger struct {
	repoID        string
	ref           string
	forceOverride bool
	result        service.ForceSyncAccepted
	err           error
}

func (m *mockForceSyncTrigger) TriggerForceSync(repoID, ref string, forceOverride bool) (service.ForceSyncAccepted, error) {
	m.repoID = repoID
	m.ref = ref
	m.forceOverride = forceOverride
	if m.err != nil {
		return service.ForceSyncAccepted{}, m.err
	}
	if m.result.JobID == "" {
		return service.ForceSyncAccepted{
			RepoID: repoID,
			Ref:    ref,
			JobID:  "job_force12345678",
			Mode:   "fresh",
			Purged: map[string]int64{"adpilot_repo_sync.repositories": 1},
		}, nil
	}
	return m.result, nil
}

func TestAdminRepoSyncAccepted(t *testing.T) {
	trigger := &mockSyncTrigger{jobID: "job_abc123"}
	server := httpserver.New("repo-sync-service", func() bool { return true }, trigger, nil, nil, nil, nil, nil, "")

	req := httptest.NewRequest(http.MethodPost, "/admin/repos/ad%2Fexample-repo/sync?ref=develop", nil)
	rec := httptest.NewRecorder()
	server.Handler().ServeHTTP(rec, req)

	if rec.Code != http.StatusAccepted {
		t.Fatalf("expected status %d, got %d body=%s", http.StatusAccepted, rec.Code, rec.Body.String())
	}
	if trigger.repoID != "ad/example-repo" {
		t.Fatalf("repo_id: got %q", trigger.repoID)
	}
	if trigger.ref != "develop" {
		t.Fatalf("ref: got %q", trigger.ref)
	}

	var resp apicontract.SyncAcceptedResponse
	if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
		t.Fatalf("decode response: %v", err)
	}
	if resp.JobID != "job_abc123" || resp.Status != "accepted" || resp.Ref != "develop" {
		t.Fatalf("unexpected response: %+v", resp)
	}
}

func TestAdminRepoSyncDefaultRef(t *testing.T) {
	trigger := &mockSyncTrigger{}
	server := httpserver.New("repo-sync-service", func() bool { return true }, trigger, nil, nil, nil, nil, nil, "")

	req := httptest.NewRequest(http.MethodPost, "/admin/repos/ad%2Fexample-repo/sync", nil)
	rec := httptest.NewRecorder()
	server.Handler().ServeHTTP(rec, req)

	if rec.Code != http.StatusAccepted {
		t.Fatalf("expected status %d, got %d", http.StatusAccepted, rec.Code)
	}

	var resp apicontract.SyncAcceptedResponse
	if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
		t.Fatalf("decode response: %v", err)
	}
	if resp.Ref != "master" {
		t.Fatalf("expected default ref master, got %q", resp.Ref)
	}
}

func TestAdminRepoSyncInvalidRepoID(t *testing.T) {
	trigger := &mockSyncTrigger{}
	server := httpserver.New("repo-sync-service", func() bool { return true }, trigger, nil, nil, nil, nil, nil, "")

	req := httptest.NewRequest(http.MethodPost, "/admin/repos/invalid/sync", nil)
	rec := httptest.NewRecorder()
	server.Handler().ServeHTTP(rec, req)

	if rec.Code != http.StatusBadRequest {
		t.Fatalf("expected status %d, got %d", http.StatusBadRequest, rec.Code)
	}
	if trigger.repoID != "" {
		t.Fatal("expected trigger not to be called")
	}
}

func TestAdminRepoSyncNestedPath(t *testing.T) {
	trigger := &mockSyncTrigger{}
	server := httpserver.New("repo-sync-service", func() bool { return true }, trigger, nil, nil, nil, nil, nil, "")

	req := httptest.NewRequest(http.MethodPost, "/admin/repos/ad%2Fadpilot-indexing-repo-sync.com/sync", nil)
	rec := httptest.NewRecorder()
	server.Handler().ServeHTTP(rec, req)

	if rec.Code != http.StatusAccepted {
		t.Fatalf("expected status %d, got %d body=%s", http.StatusAccepted, rec.Code, rec.Body.String())
	}
	if trigger.repoID != "ad/adpilot-indexing-repo-sync.com" {
		t.Fatalf("repo_id: got %q", trigger.repoID)
	}
}

func TestAdminRepoForceSyncAccepted(t *testing.T) {
	trigger := &mockForceSyncTrigger{}
	server := httpserver.New("repo-sync-service", func() bool { return true }, nil, trigger, nil, nil, nil, nil, "")

	req := httptest.NewRequest(http.MethodPost, "/admin/repos/ad%2Fexample-repo/force-sync?ref=develop", nil)
	rec := httptest.NewRecorder()
	server.Handler().ServeHTTP(rec, req)

	if rec.Code != http.StatusAccepted {
		t.Fatalf("expected status %d, got %d body=%s", http.StatusAccepted, rec.Code, rec.Body.String())
	}
	if trigger.repoID != "ad/example-repo" || trigger.ref != "develop" {
		t.Fatalf("unexpected trigger args: repo=%q ref=%q", trigger.repoID, trigger.ref)
	}

	var resp apicontract.ForceSyncAcceptedResponse
	if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
		t.Fatalf("decode response: %v", err)
	}
	if resp.Mode != "fresh" || resp.Status != "accepted" || resp.Purged["adpilot_repo_sync.repositories"] != 1 {
		t.Fatalf("unexpected response: %+v", resp)
	}
}

func TestAdminRepoForceSyncInvalidRepoID(t *testing.T) {
	trigger := &mockForceSyncTrigger{}
	server := httpserver.New("repo-sync-service", func() bool { return true }, nil, trigger, nil, nil, nil, nil, "")

	req := httptest.NewRequest(http.MethodPost, "/admin/repos/invalid/force-sync", nil)
	rec := httptest.NewRecorder()
	server.Handler().ServeHTTP(rec, req)

	if rec.Code != http.StatusBadRequest {
		t.Fatalf("expected status %d, got %d", http.StatusBadRequest, rec.Code)
	}
	if trigger.repoID != "" {
		t.Fatal("expected trigger not to be called")
	}
}

func TestAdminRepoForceSyncConflict(t *testing.T) {
	trigger := &mockForceSyncTrigger{err: service.ErrForceSyncNotAllowed}
	server := httpserver.New("repo-sync-service", func() bool { return true }, nil, trigger, nil, nil, nil, 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.StatusConflict {
		t.Fatalf("expected status %d, got %d body=%s", http.StatusConflict, rec.Code, rec.Body.String())
	}
}
