package vcs

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

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/yan1h/agent-coding-workflow/internal/infra/errs"
)

func newTestProvider(t *testing.T, h http.Handler) *GiteaProvider {
	t.Helper()
	srv := httptest.NewServer(h)
	t.Cleanup(srv.Close)
	return NewGiteaProvider(srv.URL, "test-token", 5*time.Second)
}

func TestGitea_CreatePR(t *testing.T) {
	repo := RepoRef{Owner: "owner", Name: "repo"}
	mux := http.NewServeMux()
	mux.HandleFunc("/api/v1/repos/owner/repo/pulls", func(w http.ResponseWriter, r *http.Request) {
		assert.Equal(t, http.MethodPost, r.Method)
		assert.Equal(t, "token test-token", r.Header.Get("Authorization"))
		var body map[string]any
		require.NoError(t, json.NewDecoder(r.Body).Decode(&body))
		assert.Equal(t, "feature", body["head"])
		assert.Equal(t, "main", body["base"])
		w.WriteHeader(http.StatusCreated)
		_ = json.NewEncoder(w).Encode(giteaPR{
			Number: 42, State: "open", Mergeable: true,
			HTMLURL: "https://git.jerryyan.net/owner/repo/pulls/42",
			Head:    giteaBranch{Sha: "abc123", Ref: "feature"},
		})
	})
	mux.HandleFunc("/api/v1/repos/owner/repo/commits/abc123/status", func(w http.ResponseWriter, _ *http.Request) {
		_ = json.NewEncoder(w).Encode(giteaCombinedStatus{State: "success"})
	})

	p := newTestProvider(t, mux)
	pr, err := p.CreatePR(context.Background(), CreatePRInput{
		Repo: repo, Head: "feature", Base: "main", Title: "T", Body: "B",
	})
	require.NoError(t, err)
	assert.Equal(t, int64(42), pr.Number)
	assert.Equal(t, "open", pr.State)
	assert.Equal(t, "https://git.jerryyan.net/owner/repo/pulls/42", pr.HTMLURL)
	assert.Equal(t, "success", pr.CIState)
}

func TestGitea_GetPR_MergedAndCI(t *testing.T) {
	mux := http.NewServeMux()
	mux.HandleFunc("/api/v1/repos/owner/repo/pulls/7", func(w http.ResponseWriter, _ *http.Request) {
		_ = json.NewEncoder(w).Encode(giteaPR{
			Number: 7, State: "closed", Merged: true, MergedCommitID: "deadbeef",
			HTMLURL: "https://git.jerryyan.net/owner/repo/pulls/7",
			Head:    giteaBranch{Sha: "sha7"},
		})
	})
	mux.HandleFunc("/api/v1/repos/owner/repo/commits/sha7/status", func(w http.ResponseWriter, _ *http.Request) {
		_ = json.NewEncoder(w).Encode(giteaCombinedStatus{State: "failure"})
	})

	p := newTestProvider(t, mux)
	pr, err := p.GetPR(context.Background(), RepoRef{Owner: "owner", Name: "repo"}, 7)
	require.NoError(t, err)
	assert.True(t, pr.Merged)
	assert.Equal(t, "merged", pr.State)
	assert.Equal(t, "deadbeef", pr.MergeCommitSHA)
	assert.Equal(t, "failure", pr.CIState)
}

func TestGitea_Merge(t *testing.T) {
	var mergeCalled bool
	mux := http.NewServeMux()
	mux.HandleFunc("/api/v1/repos/owner/repo/pulls/3/merge", func(w http.ResponseWriter, r *http.Request) {
		mergeCalled = true
		assert.Equal(t, http.MethodPost, r.Method)
		var body map[string]any
		require.NoError(t, json.NewDecoder(r.Body).Decode(&body))
		assert.Equal(t, "squash", body["Do"])
		w.WriteHeader(http.StatusOK)
	})
	mux.HandleFunc("/api/v1/repos/owner/repo/pulls/3", func(w http.ResponseWriter, _ *http.Request) {
		_ = json.NewEncoder(w).Encode(giteaPR{Number: 3, State: "closed", Merged: true, MergedCommitID: "mergesha", Head: giteaBranch{Sha: "h3"}})
	})
	mux.HandleFunc("/api/v1/repos/owner/repo/commits/h3/status", func(w http.ResponseWriter, _ *http.Request) {
		_ = json.NewEncoder(w).Encode(giteaCombinedStatus{State: "success"})
	})

	p := newTestProvider(t, mux)
	sha, err := p.Merge(context.Background(), RepoRef{Owner: "owner", Name: "repo"}, 3, MergeInput{Method: "squash"})
	require.NoError(t, err)
	assert.True(t, mergeCalled)
	assert.Equal(t, "mergesha", sha)
}

func TestGitea_Comment(t *testing.T) {
	var gotBody string
	mux := http.NewServeMux()
	mux.HandleFunc("/api/v1/repos/owner/repo/issues/5/comments", func(w http.ResponseWriter, r *http.Request) {
		var body map[string]any
		require.NoError(t, json.NewDecoder(r.Body).Decode(&body))
		gotBody, _ = body["body"].(string)
		w.WriteHeader(http.StatusCreated)
	})

	p := newTestProvider(t, mux)
	require.NoError(t, p.Comment(context.Background(), RepoRef{Owner: "owner", Name: "repo"}, 5, "hello"))
	assert.Equal(t, "hello", gotBody)
}

func TestGitea_ListIssues(t *testing.T) {
	mux := http.NewServeMux()
	mux.HandleFunc("/api/v1/repos/owner/repo/issues", func(w http.ResponseWriter, r *http.Request) {
		assert.Equal(t, "issues", r.URL.Query().Get("type"))
		_ = json.NewEncoder(w).Encode([]giteaIssue{
			{Number: 1, Title: "bug", State: "open", HTMLURL: "u1"},
		})
	})

	p := newTestProvider(t, mux)
	issues, err := p.ListIssues(context.Background(), RepoRef{Owner: "owner", Name: "repo"}, IssueListOptions{State: "open"})
	require.NoError(t, err)
	require.Len(t, issues, 1)
	assert.Equal(t, "bug", issues[0].Title)
}

func TestGitea_Non2xxError(t *testing.T) {
	mux := http.NewServeMux()
	mux.HandleFunc("/api/v1/repos/owner/repo/pulls/9", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusNotFound)
		_, _ = w.Write([]byte(`{"message":"not found"}`))
	})

	p := newTestProvider(t, mux)
	_, err := p.GetPR(context.Background(), RepoRef{Owner: "owner", Name: "repo"}, 9)
	require.Error(t, err)
	ae, ok := errs.As(err)
	require.True(t, ok)
	assert.Equal(t, errs.CodeNotFound, ae.Code)
}

func TestGitea_Host(t *testing.T) {
	p := NewGiteaProvider("https://git.jerryyan.net:3000", "", 0)
	assert.Equal(t, "git.jerryyan.net", p.Host())
}
