package web

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"ymhut-box/server/unified-management/internal/config"
)

func TestSetupRouterServesBuiltAssetsAndBlocksBusinessAPI(t *testing.T) {
	root := t.TempDir()
	setupDist := filepath.Join(root, "setup")
	if err := os.MkdirAll(filepath.Join(setupDist, "assets"), 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(setupDist, "index.html"), []byte(`<!doctype html><script type="module" src="/setup/assets/setup.js"></script><link rel="stylesheet" href="/setup/assets/setup.css">`), 0o644); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(setupDist, "assets", "setup.css"), []byte(`body{}`), 0o644); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(setupDist, "assets", "setup.js"), []byte(`console.log("setup")`), 0o644); err != nil {
		t.Fatal(err)
	}
	handler := NewSetupRouter(setupConfig(root, setupDist))

	for _, item := range []struct {
		path string
		want int
		typ  string
	}{
		{"/setup", http.StatusOK, "text/html"},
		{"/setup/assets/setup.css", http.StatusOK, "text/css"},
		{"/setup/assets/setup.js", http.StatusOK, "javascript"},
		{"/api/client/bootstrap", http.StatusServiceUnavailable, "application/json"},
	} {
		req := httptest.NewRequest(http.MethodGet, item.path, nil)
		res := httptest.NewRecorder()
		handler.ServeHTTP(res, req)
		if res.Code != item.want {
			t.Fatalf("%s returned %d: %s", item.path, res.Code, res.Body.String())
		}
		if got := res.Header().Get("Content-Type"); !strings.Contains(got, item.typ) {
			t.Fatalf("%s content-type = %q, want %q", item.path, got, item.typ)
		}
	}
}

func TestSetupSQLiteCompleteCreatesConfigAndDefaultAdmin(t *testing.T) {
	root := t.TempDir()
	handler := NewSetupRouter(setupConfig(root, filepath.Join(root, "missing-setup-dist")))

	body := bytes.NewBufferString(`{"provider":"sqlite","baseUrl":"https://update.ymhut.cn","sqlitePath":"storage/unified.sqlite"}`)
	req := httptest.NewRequest(http.MethodPost, "/api/setup/complete", body)
	res := httptest.NewRecorder()
	handler.ServeHTTP(res, req)
	if res.Code != http.StatusOK {
		t.Fatalf("complete returned %d: %s", res.Code, res.Body.String())
	}
	data, err := os.ReadFile(filepath.Join(root, "config.json"))
	if err != nil {
		t.Fatal(err)
	}
	var cfg config.Config
	if err := json.Unmarshal(data, &cfg); err != nil {
		t.Fatal(err)
	}
	if !cfg.Initialized || cfg.Database.Provider != "sqlite" {
		t.Fatalf("unexpected config: %#v", cfg)
	}
	if _, err := os.Stat(filepath.Join(root, "storage", "unified.sqlite")); err != nil {
		t.Fatal(err)
	}
}

func TestSetupSQLiteIgnoresStructuredMySQLDefaults(t *testing.T) {
	root := t.TempDir()
	handler := NewSetupRouter(setupConfig(root, filepath.Join(root, "missing-setup-dist")))

	body := bytes.NewBufferString(`{"provider":"sqlite","baseUrl":"https://update.ymhut.cn","sqlitePath":"storage/unified.sqlite","mysql":{"host":"127.0.0.1","port":3306,"database":"ymhut_unified","username":"","password":"","charset":"utf8mb4","parseTime":true,"tls":"false"}}`)
	req := httptest.NewRequest(http.MethodPost, "/api/setup/database/test", body)
	res := httptest.NewRecorder()
	handler.ServeHTTP(res, req)
	if res.Code != http.StatusOK {
		t.Fatalf("sqlite test returned %d: %s", res.Code, res.Body.String())
	}
	if strings.Contains(res.Body.String(), "mysql username is required") {
		t.Fatalf("sqlite test should ignore structured mysql defaults: %s", res.Body.String())
	}
}

func TestSetupStructuredMySQLValidationReturnsFailureWithoutSaving(t *testing.T) {
	root := t.TempDir()
	handler := NewSetupRouter(setupConfig(root, filepath.Join(root, "missing-setup-dist")))

	body := bytes.NewBufferString(`{"provider":"mysql","mysql":{"host":"127.0.0.1","port":1,"database":"ymhut","username":"root","password":"secret","charset":"utf8mb4","parseTime":true,"tls":"false"}}`)
	req := httptest.NewRequest(http.MethodPost, "/api/setup/database/test", body)
	res := httptest.NewRecorder()
	handler.ServeHTTP(res, req)
	if res.Code != http.StatusBadGateway {
		t.Fatalf("mysql test returned %d, want 502: %s", res.Code, res.Body.String())
	}
	if _, err := os.Stat(filepath.Join(root, "config.json")); !os.IsNotExist(err) {
		t.Fatalf("config should not be written on failed test: %v", err)
	}
	if strings.Contains(res.Body.String(), "secret") {
		t.Fatalf("response leaked password: %s", res.Body.String())
	}
}

func setupConfig(root, setupDist string) *config.Config {
	return &config.Config{
		BaseDir:     root,
		ConfigPath:  filepath.Join(root, "config.json"),
		BaseURL:     "https://update.ymhut.cn",
		StorageDir:  filepath.Join(root, "storage"),
		SetupWebDir: setupDist,
		Database: config.DatabaseConfig{
			Provider:          "sqlite",
			SQLitePath:        filepath.Join(root, "storage", "unified.sqlite"),
			HealthIntervalSec: 30,
			MaxOpenConns:      1,
			MaxIdleConns:      1,
		},
	}
}
