package test_test

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/yaoapp/gou/application"
	"github.com/yaoapp/yao/config"
	"github.com/yaoapp/yao/engine"
	"github.com/yaoapp/yao/sui/core"
	suitest "github.com/yaoapp/yao/sui/test"
	"github.com/yaoapp/yao/test"
)

func prepare(t *testing.T) {
	t.Helper()
	test.Prepare(t, config.Conf)
	_, err := engine.Load(config.Conf, engine.LoadOption{Action: "sui.test"})
	require.NoError(t, err)
}

func requireAgentSUI(t *testing.T) {
	t.Helper()
	_, has := core.SUIs["agent"]
	if !has {
		t.Skip("no 'agent' SUI loaded")
	}
}

// --- happy path (dashboard page: 4 pass) ---

func TestDiscoverPageTests(t *testing.T) {
	prepare(t)
	defer test.Clean()
	requireAgentSUI(t)

	opts := &suitest.Options{
		SUIID:    "agent",
		Template: "agent",
		Page:     "tests.sui-pages/dashboard",
		JSON:     true,
	}

	runner, err := suitest.NewRunner(opts)
	require.NoError(t, err)

	report, err := runner.Run()
	require.NoError(t, err)
	require.NotNil(t, report)

	assert.Greater(t, report.Summary.Total, 0)
	assert.Greater(t, len(report.Pages), 0)
}

func TestRunnerExecuteTests(t *testing.T) {
	prepare(t)
	defer test.Clean()
	requireAgentSUI(t)

	opts := &suitest.Options{
		SUIID:    "agent",
		Template: "agent",
		Page:     "tests.sui-pages/dashboard",
		JSON:     true,
	}

	runner, err := suitest.NewRunner(opts)
	require.NoError(t, err)

	report, err := runner.Run()
	require.NoError(t, err)

	assert.Equal(t, "sui_backend_test", report.Type)
	assert.False(t, report.Metadata.StartedAt.IsZero())
	assert.False(t, report.Metadata.CompletedAt.IsZero())
	assert.Equal(t, 4, report.Summary.Total)
	assert.Equal(t, 4, report.Summary.Passed)
	assert.Equal(t, 0, report.Summary.Failed)
	assert.False(t, report.HasFailures())
}

func TestRunnerWithRunFilter(t *testing.T) {
	prepare(t)
	defer test.Clean()
	requireAgentSUI(t)

	opts := &suitest.Options{
		SUIID:    "agent",
		Template: "agent",
		Page:     "tests.sui-pages/dashboard",
		Run:      "TestGetDashboard",
		JSON:     true,
	}

	runner, err := suitest.NewRunner(opts)
	require.NoError(t, err)

	report, err := runner.Run()
	require.NoError(t, err)
	assert.Equal(t, 1, report.Summary.Total)
	assert.Equal(t, 1, report.Summary.Passed)
}

func TestRunnerFailFast(t *testing.T) {
	prepare(t)
	defer test.Clean()
	requireAgentSUI(t)

	opts := &suitest.Options{
		SUIID:    "agent",
		Template: "agent",
		Page:     "tests.sui-pages/dashboard",
		FailFast: true,
		JSON:     true,
	}

	runner, err := suitest.NewRunner(opts)
	require.NoError(t, err)

	report, err := runner.Run()
	require.NoError(t, err)
	assert.Equal(t, 0, report.Summary.Failed)
}

// --- errors page: covers fail, skip, setAuthorized, reset ---

func TestRunnerWithFailAndSkip(t *testing.T) {
	prepare(t)
	defer test.Clean()
	requireAgentSUI(t)

	opts := &suitest.Options{
		SUIID:    "agent",
		Template: "agent",
		Page:     "tests.sui-pages/errors",
		JSON:     true,
	}

	runner, err := suitest.NewRunner(opts)
	require.NoError(t, err)

	report, err := runner.Run()
	require.NoError(t, err)
	require.NotNil(t, report)

	assert.Equal(t, 6, report.Summary.Total)
	assert.Equal(t, 2, report.Summary.Failed, "TestAlwaysFail + TestRuntimeError should fail")
	assert.Equal(t, 1, report.Summary.Skipped, "TestAlwaysSkip should be skipped")
	assert.Equal(t, 3, report.Summary.Passed)
	assert.True(t, report.HasFailures())

	page := report.Pages[0]
	for _, r := range page.Results {
		switch r.Name {
		case "TestAlwaysFail":
			assert.Equal(t, "failed", r.Status)
			assert.NotEmpty(t, r.Error)
		case "TestAlwaysSkip":
			assert.Equal(t, "skipped", r.Status)
		case "TestSetAuthorizedAndReset":
			assert.Equal(t, "passed", r.Status)
		case "TestCallWithRequestRender":
			assert.Equal(t, "passed", r.Status)
		case "TestCallNonExistentMethod":
			assert.Equal(t, "passed", r.Status)
		case "TestRuntimeError":
			assert.Equal(t, "error", r.Status)
			assert.Contains(t, r.Error, "deliberate execution error")
		}
	}
}

func TestRunnerFailFastStopsOnError(t *testing.T) {
	prepare(t)
	defer test.Clean()
	requireAgentSUI(t)

	opts := &suitest.Options{
		SUIID:    "agent",
		Template: "agent",
		Page:     "tests.sui-pages/errors",
		FailFast: true,
		JSON:     true,
	}

	runner, err := suitest.NewRunner(opts)
	require.NoError(t, err)

	report, err := runner.Run()
	require.NoError(t, err)
	assert.Equal(t, 1, report.Summary.Failed)
	// fail-fast: executed only up to the first failure
	executed := report.Summary.Passed + report.Summary.Failed + report.Summary.Skipped
	assert.Less(t, executed, report.Summary.Total, "fail-fast should stop before running all tests")
}

// --- verbose (non-JSON) output: covers printHeader, printTestResult, printSummary ---

func TestRunnerVerboseAllPass(t *testing.T) {
	prepare(t)
	defer test.Clean()
	requireAgentSUI(t)

	opts := &suitest.Options{
		SUIID:    "agent",
		Template: "agent",
		Page:     "tests.sui-pages/dashboard",
		Verbose:  true,
		JSON:     false,
	}

	runner, err := suitest.NewRunner(opts)
	require.NoError(t, err)

	report, err := runner.Run()
	require.NoError(t, err)
	assert.Equal(t, 4, report.Summary.Passed)
}

func TestRunnerVerboseWithFailures(t *testing.T) {
	prepare(t)
	defer test.Clean()
	requireAgentSUI(t)

	opts := &suitest.Options{
		SUIID:    "agent",
		Template: "agent",
		Page:     "tests.sui-pages/errors",
		Verbose:  true,
		JSON:     false,
	}

	runner, err := suitest.NewRunner(opts)
	require.NoError(t, err)

	report, err := runner.Run()
	require.NoError(t, err)
	assert.Greater(t, report.Summary.Failed, 0)
}

func TestRunnerVerboseWithData(t *testing.T) {
	prepare(t)
	defer test.Clean()
	requireAgentSUI(t)

	opts := &suitest.Options{
		SUIID:    "agent",
		Template: "agent",
		Page:     "tests.sui-pages/dashboard",
		Data:     `{"key":"value"}`,
		Verbose:  true,
		JSON:     false,
	}

	runner, err := suitest.NewRunner(opts)
	require.NoError(t, err)

	report, err := runner.Run()
	require.NoError(t, err)
	assert.Equal(t, 4, report.Summary.Passed)
}

// --- error paths ---

func TestNewRunnerInvalidSUI(t *testing.T) {
	prepare(t)
	defer test.Clean()

	_, err := suitest.NewRunner(&suitest.Options{SUIID: "nonexistent", Template: "default"})
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "not found")
}

func TestNewRunnerInvalidTemplate(t *testing.T) {
	prepare(t)
	defer test.Clean()
	requireAgentSUI(t)

	_, err := suitest.NewRunner(&suitest.Options{SUIID: "agent", Template: "nonexistent-tmpl"})
	assert.Error(t, err)
}

func TestRunnerNoMatchingPage(t *testing.T) {
	prepare(t)
	defer test.Clean()
	requireAgentSUI(t)

	runner, err := suitest.NewRunner(&suitest.Options{
		SUIID: "agent", Template: "agent", Page: "xyz-no-match", JSON: true,
	})
	require.NoError(t, err)

	report, err := runner.Run()
	require.NoError(t, err)
	assert.Equal(t, 0, report.Summary.Total)
}

func TestRunnerNoMatchingRun(t *testing.T) {
	prepare(t)
	defer test.Clean()
	requireAgentSUI(t)

	runner, err := suitest.NewRunner(&suitest.Options{
		SUIID: "agent", Template: "agent", Page: "tests.sui-pages/dashboard",
		Run: "NoSuchFunction", JSON: true,
	})
	require.NoError(t, err)

	report, err := runner.Run()
	require.NoError(t, err)
	assert.Equal(t, 0, report.Summary.Total)
}

func TestRunnerVerboseNoTestFiles(t *testing.T) {
	prepare(t)
	defer test.Clean()
	requireAgentSUI(t)

	runner, err := suitest.NewRunner(&suitest.Options{
		SUIID: "agent", Template: "agent", Page: "xyz-no-match", JSON: false,
	})
	require.NoError(t, err)

	report, err := runner.Run()
	require.NoError(t, err)
	assert.Equal(t, 0, report.Summary.Total)
}

// --- types ---

func TestLoadPageConfig(t *testing.T) {
	prepare(t)
	defer test.Clean()

	cfgFile := "assistants/tests/sui-pages/pages/dashboard/dashboard.config"
	exists, _ := application.App.Exists(cfgFile)
	if !exists {
		t.Skipf("test config %s not found", cfgFile)
	}

	cfg, err := suitest.LoadPageConfig(cfgFile)
	assert.NoError(t, err)
	assert.NotNil(t, cfg)
	assert.Equal(t, "Test Dashboard", cfg.Title)
}

func TestLoadPageConfigNotExist(t *testing.T) {
	prepare(t)
	defer test.Clean()

	cfg, err := suitest.LoadPageConfig("nonexistent/path.config")
	assert.NoError(t, err)
	assert.Nil(t, cfg)
}

func TestReportHasFailures(t *testing.T) {
	r := &suitest.Report{Summary: &suitest.TestSummary{Failed: 1}}
	assert.True(t, r.HasFailures())
	r.Summary.Failed = 0
	assert.False(t, r.HasFailures())
}
