package changerequest

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

	"github.com/go-chi/chi/v5"
	"github.com/google/uuid"
	"github.com/stretchr/testify/require"

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

// fakeSvc implements Service for handler tests.
type fakeSvc struct {
	mergeErr error
	cr       *ChangeRequest
}

func (f *fakeSvc) Create(_ context.Context, c Caller, wsID uuid.UUID, _ CreateInput) (*ChangeRequest, error) {
	return &ChangeRequest{ID: uuid.New(), WorkspaceID: wsID, Number: 1, State: StateOpen, ReviewVerdict: VerdictPending, CreatedBy: c.UserID}, nil
}
func (f *fakeSvc) Get(_ context.Context, _ Caller, id uuid.UUID) (*ChangeRequest, error) {
	return &ChangeRequest{ID: id, State: StateOpen}, nil
}
func (f *fakeSvc) GetByNumber(_ context.Context, _ Caller, _ string, n int) (*ChangeRequest, error) {
	return &ChangeRequest{ID: uuid.New(), Number: n}, nil
}
func (f *fakeSvc) ListByWorkspace(_ context.Context, _ Caller, wsID uuid.UUID) ([]*ChangeRequest, error) {
	return []*ChangeRequest{{ID: uuid.New(), WorkspaceID: wsID, Number: 1}}, nil
}
func (f *fakeSvc) Review(_ context.Context, _ Caller, id uuid.UUID, v Verdict, _ string) (*ChangeRequest, error) {
	return &ChangeRequest{ID: id, ReviewVerdict: v}, nil
}
func (f *fakeSvc) Merge(_ context.Context, _ Caller, id uuid.UUID, _ string) (*ChangeRequest, error) {
	if f.mergeErr != nil {
		return nil, f.mergeErr
	}
	return &ChangeRequest{ID: id, State: StateMerged}, nil
}
func (f *fakeSvc) SyncStatus(_ context.Context, _ Caller, id uuid.UUID) (*ChangeRequest, error) {
	return &ChangeRequest{ID: id, CIState: CISuccess}, nil
}

type fakeResolver struct{ valid map[string]uuid.UUID }

func (f *fakeResolver) ResolveSession(_ context.Context, token string) (uuid.UUID, bool, error) {
	uid, ok := f.valid[token]
	return uid, ok, nil
}

type fakeAdmin struct{}

func (fakeAdmin) IsAdmin(_ context.Context, _ uuid.UUID) (bool, error) { return false, nil }

func newTestHandler(svc Service, tok string, uid uuid.UUID) chi.Router {
	resolver := &fakeResolver{valid: map[string]uuid.UUID{tok: uid}}
	h := NewHandler(svc, resolver, fakeAdmin{})
	r := chi.NewRouter()
	r.Use(middleware.RequestID)
	h.Mount(r)
	return r
}

func TestHandler_Unauthenticated(t *testing.T) {
	t.Parallel()
	r := newTestHandler(&fakeSvc{}, "tok", uuid.New())
	req := httptest.NewRequest("GET", "/api/v1/workspaces/"+uuid.NewString()+"/change-requests", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	require.Equal(t, http.StatusUnauthorized, w.Code)
}

func TestHandler_CreateAndList(t *testing.T) {
	t.Parallel()
	uid := uuid.New()
	r := newTestHandler(&fakeSvc{}, "tok", uid)
	wsID := uuid.New()

	body, _ := json.Marshal(createReq{SourceBranch: "feat", Title: "T"})
	req := httptest.NewRequest("POST", "/api/v1/workspaces/"+wsID.String()+"/change-requests", bytes.NewReader(body))
	req.Header.Set("Authorization", "Bearer tok")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	require.Equal(t, http.StatusCreated, w.Code)

	req = httptest.NewRequest("GET", "/api/v1/workspaces/"+wsID.String()+"/change-requests", nil)
	req.Header.Set("Authorization", "Bearer tok")
	w = httptest.NewRecorder()
	r.ServeHTTP(w, req)
	require.Equal(t, http.StatusOK, w.Code)
}

func TestHandler_MergeGateReturns412(t *testing.T) {
	t.Parallel()
	svc := &fakeSvc{mergeErr: errs.New(errs.CodeFailedPrecondition, "merge blocked: review not approved")}
	r := newTestHandler(svc, "tok", uuid.New())

	req := httptest.NewRequest("POST", "/api/v1/change-requests/"+uuid.NewString()+"/merge", nil)
	req.Header.Set("Authorization", "Bearer tok")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	require.Equal(t, http.StatusPreconditionFailed, w.Code)
}

func TestHandler_MergeHappyPath(t *testing.T) {
	t.Parallel()
	r := newTestHandler(&fakeSvc{}, "tok", uuid.New())

	req := httptest.NewRequest("POST", "/api/v1/change-requests/"+uuid.NewString()+"/merge", bytes.NewReader([]byte(`{"method":"squash"}`)))
	req.Header.Set("Authorization", "Bearer tok")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	require.Equal(t, http.StatusOK, w.Code)

	var resp changeRequestResp
	require.NoError(t, json.NewDecoder(w.Body).Decode(&resp))
	require.Equal(t, string(StateMerged), resp.State)
}

func TestHandler_Review(t *testing.T) {
	t.Parallel()
	r := newTestHandler(&fakeSvc{}, "tok", uuid.New())

	body, _ := json.Marshal(reviewReq{Verdict: "approved", Note: "lgtm"})
	req := httptest.NewRequest("POST", "/api/v1/change-requests/"+uuid.NewString()+"/review", bytes.NewReader(body))
	req.Header.Set("Authorization", "Bearer tok")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	require.Equal(t, http.StatusOK, w.Code)

	var resp changeRequestResp
	require.NoError(t, json.NewDecoder(w.Body).Decode(&resp))
	require.Equal(t, "approved", resp.ReviewVerdict)
}
