package web

import (
	"encoding/json"
	"net/http"
	"strings"
	"testing"
)

// TestOpenAPISpec проверяет, что /api/openapi.json отдаёт валидную спецификацию
func TestOpenAPISpec(t *testing.T) {
	ts, _ := makeTestServer(t)

	resp, err := http.Get(ts.URL + "/api/openapi.json")
	if err != nil {
		t.Fatalf("GET /api/openapi.json: %s", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", resp.StatusCode)
	}
	if ct := resp.Header.Get("Content-Type"); !strings.HasPrefix(ct, "application/json") {
		t.Errorf("expected application/json content-type, got %s", ct)
	}

	var spec map[string]interface{}
	if err := json.NewDecoder(resp.Body).Decode(&spec); err != nil {
		t.Fatalf("cannot decode spec: %s", err)
	}

	// базовые поля OpenAPI 3.0
	if v, ok := spec["openapi"].(string); !ok || !strings.HasPrefix(v, "3.0") {
		t.Errorf("expected openapi 3.0.x, got %v", spec["openapi"])
	}

	info, ok := spec["info"].(map[string]interface{})
	if !ok {
		t.Fatal("missing 'info' section")
	}
	if _, ok := info["title"].(string); !ok {
		t.Error("missing info.title")
	}
	if _, ok := info["version"].(string); !ok {
		t.Error("missing info.version")
	}

	// сервер должен быть указан
	servers, ok := spec["servers"].([]interface{})
	if !ok || len(servers) == 0 {
		t.Error("expected at least one server")
	}

	// пути
	paths, ok := spec["paths"].(map[string]interface{})
	if !ok {
		t.Fatal("missing 'paths' section")
	}

	expectedPaths := []string{
		"/api/playlists",
		"/api/playlists/{code}",
		"/api/playlists/{code}/channels",
		"/api/version",
		"/api/health",
		"/api/stats",
	}
	for _, p := range expectedPaths {
		if _, ok := paths[p]; !ok {
			t.Errorf("missing path %s in spec", p)
		}
	}

	// компоненты/схемы
	components, ok := spec["components"].(map[string]interface{})
	if !ok {
		t.Fatal("missing 'components' section")
	}
	schemas, ok := components["schemas"].(map[string]interface{})
	if !ok {
		t.Fatal("missing 'components.schemas' section")
	}

	expectedSchemas := []string{
		"PlaylistListResponse",
		"PlaylistResponse",
		"Playlist",
		"ChannelListResponse",
		"Channel",
		"Group",
		"Attribute",
		"VersionResponse",
		"HealthResponse",
		"StatsResponse",
		"ErrorResponse",
	}
	for _, s := range expectedSchemas {
		if _, ok := schemas[s]; !ok {
			t.Errorf("missing schema %s in components.schemas", s)
		}
	}
}

// TestSwaggerUI проверяет, что /api/ отдаёт HTML-страницу Swagger UI
// и что CSS/JS-ассеты доступны без внешних зависимостей
func TestSwaggerUI(t *testing.T) {
	ts, _ := makeTestServer(t)

	// /api → редирект на /api/
	noRedirect := &http.Client{CheckRedirect: func(req *http.Request, via []*http.Request) error {
		return http.ErrUseLastResponse
	}}
	resp, err := noRedirect.Get(ts.URL + "/api")
	if err != nil {
		t.Fatalf("GET /api: %s", err)
	}
	resp.Body.Close()
	if resp.StatusCode != http.StatusMovedPermanently {
		t.Fatalf("expected 301 redirect, got %d", resp.StatusCode)
	}

	// /api/ → HTML-страница
	resp, err = http.Get(ts.URL + "/api/")
	if err != nil {
		t.Fatalf("GET /api/: %s", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", resp.StatusCode)
	}
	if ct := resp.Header.Get("Content-Type"); !strings.HasPrefix(ct, "text/html") {
		t.Errorf("expected text/html content-type, got %s", ct)
	}

	body := fetchBody(t, resp)
	if !strings.Contains(body, "swagger-ui") {
		t.Error("Swagger UI page should contain 'swagger-ui' reference")
	}
	if !strings.Contains(body, "/api/openapi.json") {
		t.Error("Swagger UI page should reference /api/openapi.json")
	}
	// проверяем, что нет ссылок на CDN
	if strings.Contains(body, "cdn.jsdelivr.net") || strings.Contains(body, "unpkg.com") {
		t.Error("Swagger UI page should not reference any CDN")
	}

	// CSS-ассет
	respCSS, err := http.Get(ts.URL + "/api/swagger-ui.css")
	if err != nil {
		t.Fatalf("GET /api/swagger-ui.css: %s", err)
	}
	defer respCSS.Body.Close()
	if respCSS.StatusCode != http.StatusOK {
		t.Fatalf("expected 200 for CSS, got %d", respCSS.StatusCode)
	}
	if ct := respCSS.Header.Get("Content-Type"); !strings.HasPrefix(ct, "text/css") {
		t.Errorf("expected text/css content-type, got %s", ct)
	}

	// JS-ассет
	respJS, err := http.Get(ts.URL + "/api/swagger-ui-bundle.js")
	if err != nil {
		t.Fatalf("GET /api/swagger-ui-bundle.js: %s", err)
	}
	defer respJS.Body.Close()
	if respJS.StatusCode != http.StatusOK {
		t.Fatalf("expected 200 for JS, got %d", respJS.StatusCode)
	}
	if ct := respJS.Header.Get("Content-Type"); !strings.Contains(ct, "javascript") {
		t.Errorf("expected javascript content-type, got %s", ct)
	}
}
