package middleware

import (
	"net/http/httptest"
	"testing"

	"Noooste/garage-ui/internal/config"

	"github.com/gofiber/fiber/v3"
)

func newCORSApp(t *testing.T, cfg *config.CORSConfig) *fiber.App {
	t.Helper()
	app := fiber.New()
	app.Use(CORSMiddleware(cfg))
	app.Get("/x", func(c fiber.Ctx) error {
		return c.SendString("ok")
	})
	return app
}

func TestCORS_Disabled_NoHeadersSet(t *testing.T) {
	cfg := &config.CORSConfig{Enabled: false}
	app := newCORSApp(t, cfg)

	req := httptest.NewRequest("GET", "/x", nil)
	req.Header.Set("Origin", "https://foo.example")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("app.Test: %v", err)
	}
	if resp.StatusCode != 200 {
		t.Fatalf("status = %d, want 200", resp.StatusCode)
	}
	if got := resp.Header.Get("Access-Control-Allow-Origin"); got != "" {
		t.Errorf("Allow-Origin = %q, want empty", got)
	}
}

func TestCORS_Enabled_AllowedOriginEchoes(t *testing.T) {
	cfg := &config.CORSConfig{
		Enabled:        true,
		AllowedOrigins: []string{"https://ok.example"},
		AllowedMethods: []string{"GET", "POST"},
		AllowedHeaders: []string{"Authorization", "Content-Type"},
		MaxAge:         300,
	}
	app := newCORSApp(t, cfg)

	req := httptest.NewRequest("GET", "/x", nil)
	req.Header.Set("Origin", "https://ok.example")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("app.Test: %v", err)
	}
	if resp.StatusCode != 200 {
		t.Fatalf("status = %d, want 200", resp.StatusCode)
	}
	if got := resp.Header.Get("Access-Control-Allow-Origin"); got != "https://ok.example" {
		t.Errorf("Allow-Origin = %q", got)
	}
	if got := resp.Header.Get("Vary"); got != "Origin" {
		t.Errorf("Vary = %q, want Origin", got)
	}
}

func TestCORS_Enabled_OriginNotInList_NoHeaders(t *testing.T) {
	cfg := &config.CORSConfig{
		Enabled:        true,
		AllowedOrigins: []string{"https://ok.example"},
	}
	app := newCORSApp(t, cfg)

	req := httptest.NewRequest("GET", "/x", nil)
	req.Header.Set("Origin", "https://evil.example")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("app.Test: %v", err)
	}
	if resp.StatusCode != 200 {
		t.Fatalf("status = %d, want 200", resp.StatusCode)
	}
	if got := resp.Header.Get("Access-Control-Allow-Origin"); got != "" {
		t.Errorf("Allow-Origin = %q, want empty", got)
	}
}

func TestCORS_Enabled_NoOriginHeader_NoCORSHeaders(t *testing.T) {
	cfg := &config.CORSConfig{
		Enabled:        true,
		AllowedOrigins: []string{"*"},
	}
	app := newCORSApp(t, cfg)

	req := httptest.NewRequest("GET", "/x", nil)
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("app.Test: %v", err)
	}
	if got := resp.Header.Get("Access-Control-Allow-Origin"); got != "" {
		t.Errorf("Allow-Origin = %q, want empty (no Origin header)", got)
	}
}

func TestCORS_Wildcard_NoCredentials_AllowsAnyOrigin(t *testing.T) {
	cfg := &config.CORSConfig{
		Enabled:          true,
		AllowedOrigins:   []string{"*"},
		AllowCredentials: false,
	}
	app := newCORSApp(t, cfg)

	req := httptest.NewRequest("GET", "/x", nil)
	req.Header.Set("Origin", "https://anywhere.example")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("app.Test: %v", err)
	}
	if got := resp.Header.Get("Access-Control-Allow-Origin"); got != "https://anywhere.example" {
		t.Errorf("Allow-Origin = %q, want echo", got)
	}
	if got := resp.Header.Get("Access-Control-Allow-Credentials"); got != "" {
		t.Errorf("Allow-Credentials set unexpectedly: %q", got)
	}
}

func TestCORS_Wildcard_WithCredentials_RejectsUnlistedOrigin(t *testing.T) {
	cfg := &config.CORSConfig{
		Enabled:          true,
		AllowedOrigins:   []string{"*"},
		AllowCredentials: true,
	}
	app := newCORSApp(t, cfg)

	req := httptest.NewRequest("GET", "/x", nil)
	req.Header.Set("Origin", "https://evil.example")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("app.Test: %v", err)
	}
	if got := resp.Header.Get("Access-Control-Allow-Origin"); got != "" {
		t.Errorf("Allow-Origin = %q, want empty (wildcard+creds must not honor *)", got)
	}
}

func TestCORS_ExactMatch_WithCredentials_SetsAllowCredentials(t *testing.T) {
	cfg := &config.CORSConfig{
		Enabled:          true,
		AllowedOrigins:   []string{"https://ok.example"},
		AllowCredentials: true,
	}
	app := newCORSApp(t, cfg)

	req := httptest.NewRequest("GET", "/x", nil)
	req.Header.Set("Origin", "https://ok.example")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("app.Test: %v", err)
	}
	if got := resp.Header.Get("Access-Control-Allow-Origin"); got != "https://ok.example" {
		t.Errorf("Allow-Origin = %q", got)
	}
	if got := resp.Header.Get("Access-Control-Allow-Credentials"); got != "true" {
		t.Errorf("Allow-Credentials = %q, want true", got)
	}
}

func TestCORS_Preflight_AllowedOrigin_Returns204WithHeaders(t *testing.T) {
	cfg := &config.CORSConfig{
		Enabled:        true,
		AllowedOrigins: []string{"https://ok.example"},
		AllowedMethods: []string{"GET", "POST", "PUT"},
		AllowedHeaders: []string{"Authorization"},
		MaxAge:         600,
	}
	app := newCORSApp(t, cfg)

	req := httptest.NewRequest("OPTIONS", "/x", nil)
	req.Header.Set("Origin", "https://ok.example")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("app.Test: %v", err)
	}
	if resp.StatusCode != 204 {
		t.Fatalf("status = %d, want 204", resp.StatusCode)
	}
	if got := resp.Header.Get("Access-Control-Allow-Methods"); got != "GET, POST, PUT" {
		t.Errorf("Allow-Methods = %q", got)
	}
	if got := resp.Header.Get("Access-Control-Allow-Headers"); got != "Authorization" {
		t.Errorf("Allow-Headers = %q", got)
	}
	if got := resp.Header.Get("Access-Control-Max-Age"); got != "600" {
		t.Errorf("Max-Age = %q", got)
	}
}

func TestCORS_Preflight_DisallowedOrigin_Returns204NoHeaders(t *testing.T) {
	cfg := &config.CORSConfig{
		Enabled:        true,
		AllowedOrigins: []string{"https://ok.example"},
	}
	app := newCORSApp(t, cfg)

	req := httptest.NewRequest("OPTIONS", "/x", nil)
	req.Header.Set("Origin", "https://evil.example")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("app.Test: %v", err)
	}
	if resp.StatusCode != 204 {
		t.Fatalf("status = %d, want 204", resp.StatusCode)
	}
	if got := resp.Header.Get("Access-Control-Allow-Origin"); got != "" {
		t.Errorf("Allow-Origin set for disallowed preflight: %q", got)
	}
}

func TestCORS_EmptyAllowedMethods_NoAllowMethodsHeader(t *testing.T) {
	cfg := &config.CORSConfig{
		Enabled:        true,
		AllowedOrigins: []string{"https://ok.example"},
		// AllowedMethods intentionally nil
	}
	app := newCORSApp(t, cfg)

	req := httptest.NewRequest("GET", "/x", nil)
	req.Header.Set("Origin", "https://ok.example")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("app.Test: %v", err)
	}
	if got := resp.Header.Get("Access-Control-Allow-Methods"); got != "" {
		t.Errorf("Allow-Methods set when list empty: %q", got)
	}
}

func TestCORS_MaxAgeZero_NoMaxAgeHeader(t *testing.T) {
	cfg := &config.CORSConfig{
		Enabled:        true,
		AllowedOrigins: []string{"https://ok.example"},
		MaxAge:         0,
	}
	app := newCORSApp(t, cfg)

	req := httptest.NewRequest("GET", "/x", nil)
	req.Header.Set("Origin", "https://ok.example")
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("app.Test: %v", err)
	}
	if got := resp.Header.Get("Access-Control-Max-Age"); got != "" {
		t.Errorf("Max-Age set when zero: %q", got)
	}
}

func TestIsAllowedOrigin(t *testing.T) {
	cases := []struct {
		name             string
		origin           string
		allowed          []string
		allowCredentials bool
		want             bool
	}{
		{"exact match", "https://ok.example", []string{"https://ok.example"}, false, true},
		{"exact match with creds", "https://ok.example", []string{"https://ok.example"}, true, true},
		{"wildcard without creds matches", "https://any.example", []string{"*"}, false, true},
		{"wildcard with creds rejected", "https://any.example", []string{"*"}, true, false},
		{"no match", "https://evil.example", []string{"https://ok.example"}, false, false},
		{"empty allowed list", "https://ok.example", nil, false, false},
		{"multiple entries — exact hit", "https://b.example", []string{"https://a.example", "https://b.example"}, false, true},
	}
	for _, tc := range cases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			if got := isAllowedOrigin(tc.origin, tc.allowed, tc.allowCredentials); got != tc.want {
				t.Errorf("isAllowedOrigin(%q, %v, %v) = %v, want %v",
					tc.origin, tc.allowed, tc.allowCredentials, got, tc.want)
			}
		})
	}
}
