package testkit

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

	"apps/backend/internal/config"
	"apps/backend/internal/domain"
	"apps/backend/internal/handler"
	"apps/backend/internal/response"
	"apps/backend/internal/svc"

	"github.com/stretchr/testify/require"
	"github.com/zeromicro/go-zero/core/stores/cache"
	"github.com/zeromicro/go-zero/core/stores/redis"
	"github.com/zeromicro/go-zero/rest"
)

// FixedNow is a frozen clock for schedule tests (M4+).
var FixedNow = time.Date(2026, 7, 10, 12, 0, 0, 0, time.UTC)

// TestConfig returns a minimal in-memory config for tests.
func TestConfig() config.Config {
	var c config.Config
	c.Name = "haixun-test"
	c.Host = "127.0.0.1"
	c.Port = 0
	c.Timeout = 3000
	c.Log.Mode = "console"
	c.Log.Level = "error"
	c.Mongo.URI = "mongodb://127.0.0.1:27017"
	c.Mongo.Database = "haixun_test"
	c.CacheRedis = cache.CacheConf{{
		RedisConf: redis.RedisConf{Host: "127.0.0.1:6379", Type: "node"},
		Weight:    100,
	}}
	c.Auth.AccessSecret = "test-access"
	c.Auth.RefreshSecret = "test-refresh"
	c.Auth.AccessExpire = 3600
	c.Auth.RefreshExpire = 86400
	c.Worker.ID = "test-worker"
	c.Worker.PollIntervalMs = 100
	return c
}

// StartGateway builds a rest.Server with registered routes (not listening).
// Callers use ServeHTTP via httptest for unit/integration without bind.
func StartGateway(t *testing.T) (*rest.Server, *svc.ServiceContext) {
	t.Helper()
	c := TestConfig()
	// Avoid log spam / file mode issues in tests
	server := rest.MustNewServer(c.RestConf)
	ctx := svc.NewServiceContext(c)
	handler.RegisterHandlers(server, ctx)
	t.Cleanup(func() { server.Stop() })
	return server, ctx
}

// AssertEnvelope102000 checks success envelope shape (A-07).
func AssertEnvelope102000(t *testing.T, body []byte) response.Envelope {
	t.Helper()
	var env response.Envelope
	require.NoError(t, json.Unmarshal(body, &env))
	require.Equal(t, domain.SuccessCode, env.Code, "expected success code 102000")
	require.NotEmpty(t, env.Message)
	require.Nil(t, env.Error)
	return env
}

// AssertFailNotEmptySuccess ensures response is not 102000 with empty success (X-01).
func AssertFailNotEmptySuccess(t *testing.T, body []byte) response.Envelope {
	t.Helper()
	var env response.Envelope
	require.NoError(t, json.Unmarshal(body, &env))
	require.NotEqual(t, domain.SuccessCode, env.Code, "must not be success empty")
	require.NotEmpty(t, env.Message)
	return env
}

// GetJSON issues GET against the in-process server mux via handler registration.
// go-zero rest.Server does not expose ServeHTTP easily in all versions; we re-register
// on a plain mux for tests.
func NewTestMux(svcCtx *svc.ServiceContext) http.Handler {
	mux := http.NewServeMux()
	// Mirror routes.go paths for testkit convenience
	h := handlerProbe{svc: svcCtx}
	mux.HandleFunc("GET /api/v1/ping", h.ping)
	mux.HandleFunc("GET /api/v1/health", h.ping)
	mux.HandleFunc("GET /api/v1/_not_implemented_probe", h.notImpl)
	return mux
}

type handlerProbe struct {
	svc *svc.ServiceContext
}

func (h handlerProbe) ping(w http.ResponseWriter, r *http.Request) {
	// Import cycle avoided: call response directly with same payload as ping logic
	response.OK(w, map[string]string{"status": "ok", "service": "haixun-gateway"})
}

func (h handlerProbe) notImpl(w http.ResponseWriter, r *http.Request) {
	response.NotImplemented(w, "probe")
}

// DoGet helper
func DoGet(t *testing.T, h http.Handler, path string) *httptest.ResponseRecorder {
	t.Helper()
	req := httptest.NewRequest(http.MethodGet, path, nil)
	rr := httptest.NewRecorder()
	h.ServeHTTP(rr, req)
	return rr
}
