package api

import (
	"context"
	"io"
	"log/slog"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"fiko-ai/internal/agent"
	"fiko-ai/internal/conversation"
	"fiko-ai/internal/event"
	"fiko-ai/internal/prompt"
)

// statikTestSunucu, StaticDir verilmiş tam bir API sunucusu kurar.
func statikTestSunucu(t *testing.T, staticDir string) *Server {
	t.Helper()
	log := slog.New(slog.NewTextHandler(io.Discard, nil))
	svc := conversation.NewService(conversation.NewMemoryRepo())
	bus := event.NewMemoryBus(nil, 16)
	p := &fakeProvider{}
	ag := agent.New(svc, p, prompt.Default(), bus, log, agent.Config{})
	t.Cleanup(func() { _ = ag.Close(context.Background()) })
	// AuthKey DOLU: kimlik doğrulama zorunlu. Statik dosyalar bunun ALTINDA kalmalı.
	return New(context.Background(), Config{
		Env: "test", Version: "test", StaticDir: staticDir,
		AuthKey: "gizli-anahtar", DefaultUserID: "local",
		DefaultPageSize: 2, MaxPageSize: 5,
	}, Deps{Conversations: svc, LLM: p, Bus: bus}, log)
}

// spaDizini, index.html içeren geçici bir statik dizin üretir.
func spaDizini(t *testing.T) string {
	t.Helper()
	dir := t.TempDir()
	if err := os.WriteFile(filepath.Join(dir, "index.html"), []byte("<app-root></app-root>"), 0o644); err != nil {
		t.Fatal(err)
	}
	return dir
}

// TestStatikYayin_KabukDosyalarKimlikGerektirmez giriş EKRANI dahil SPA dosyaları
// kimliksiz erişilebilir olmalıdır.
//
// CANLI HATA (05.10.2026 — HTTPS yayını): arayüz 401 döndü, kullanıcı giriş
// ekranını bile göremedi ve "site açılmıyor" dedi. Giriş ekranı da bir statik
// dosyadır; arayüz kabuğu kimlik arkasında olamaz.
func TestStatikYayin_KabukDosyalarKimlikGerektirmez(t *testing.T) {
	s := statikTestSunucu(t, spaDizini(t))

	for _, path := range []string{"/", "/ayarlar", "/tools"} {
		req := httptest.NewRequest("GET", path, nil)
		resp, err := s.App().Test(req)
		if err != nil {
			t.Fatalf("%s: %v", path, err)
		}
		if resp.StatusCode == 401 || resp.StatusCode == 403 {
			t.Errorf("%s: SPA kabuğu kimlik gerektirmemeli, %d döndü", path, resp.StatusCode)
		}
	}
}

// TestStatikYayin_APIveWSKimlikHareketindeKalmali kritik güvenlik testidir:
// istisna SPA dosyaları içindir, VERİ uçlarına ASLA sızmamalıdır.
func TestStatikYayin_APIveWSKimlikHareketindeKalmali(t *testing.T) {
	s := statikTestSunucu(t, spaDizini(t))

	// Statik yayın varken /api hâlâ 401 vermeli.
	req := httptest.NewRequest("GET", "/api/tools", nil)
	resp, err := s.App().Test(req)
	if err != nil {
		t.Fatalf("istek: %v", err)
	}
	if resp.StatusCode != 401 {
		t.Errorf("/api kimliksiz 401 vermeli, %d döndü — istisna veri uçlarına SIZDI", resp.StatusCode)
	}

	// Doğru anahtarla geçmeli (istisna yalnız kabuğu etkiliyor).
	req2 := httptest.NewRequest("GET", "/api/tools", nil)
	req2.Header.Set(AuthHeader, "gizli-anahtar")
	resp2, err := s.App().Test(req2)
	if err != nil {
		t.Fatalf("istek: %v", err)
	}
	if resp2.StatusCode == 401 {
		t.Error("doğru anahtarla /api 401 vermemeli")
	}
}

// TestStatikYayin_YokkenKimlikZorunluKalir güvenlik regresyonudur.
//
// Tehlike: istisna koşulsuz yazılırsa, HTTP_STATIC_DIR boşken (geliştirme) "her şey
// statiktir" denir ve kimlik doğrulama TAMAMEN KALKAR — internete açıkken bu,
// sistemi tümüyle açmak demektir.
func TestStatikYayin_YokkenKimlikZorunluKalir(t *testing.T) {
	s := statikTestSunucu(t, "") // statik YOK

	req := httptest.NewRequest("GET", "/api/tools", nil)
	resp, err := s.App().Test(req)
	if err != nil {
		t.Fatalf("istek: %v", err)
	}
	if resp.StatusCode != 401 {
		t.Errorf("statik yayın yokken /api 401 vermeli, %d döndü", resp.StatusCode)
	}
}

// TestStatikYayin_PathTraversalEngelli güvenlik testidir: kullanıcı kontrollü bir
// yolla dizin DIŞINA (sertifika anahtarı, .env, işletim sistemi dosyaları) çıkamaz.
//
// İnternete açılan bir sunucuda bu açık, HTTPS sertifikasının ÖZEL ANAHTARINI
// ele geçirmeye yeter.
func TestStatikYayin_PathTraversalEngelli(t *testing.T) {
	s := statikTestSunucu(t, spaDizini(t))

	for _, p := range []string{
		"/../.env",
		"/../../server.key",
		"/..%2f..%2f.env",
		"/../cert/server.key",
	} {
		req := httptest.NewRequest("GET", p, nil)
		resp, err := s.App().Test(req)
		if err != nil {
			continue // Fiber geçersiz yolu reddediyorsa da güvenli
		}
		if resp.StatusCode == 200 {
			body, _ := io.ReadAll(resp.Body)
			if strings.Contains(string(body), "PRIVATE KEY") ||
				strings.Contains(string(body), "MONGO") ||
				strings.Contains(string(body), "FIKO_API_KEY") {
				t.Errorf("%s: DİZİN DIŞI DOSYA SIZDIRILDI!", p)
			}
		}
	}
}

// TestStatikYayin_VarlikEksikse404Doner SPA kuralı: /yok.js gibi UZANTILI
// istekler index.html ALMAMALIDIR (yoksa uygulama sessizce bozulur).
func TestStatikYayin_VarlikEksikse404Doner(t *testing.T) {
	s := statikTestSunucu(t, spaDizini(t))

	req := httptest.NewRequest("GET", "/yok.js", nil)
	resp, err := s.App().Test(req)
	if err != nil {
		t.Fatalf("istek: %v", err)
	}
	if resp.StatusCode == 200 {
		t.Error("eksik .js dosyası için index.html DÖNMEMELİ (uygulama bozulur)")
	}
}
