package api

import (
	"context"
	"io"
	"log/slog"
	"net/http"
	"testing"
	"time"

	"fiko-ai/internal/agent"
	"fiko-ai/internal/api/ws"
	"fiko-ai/internal/conversation"
	"fiko-ai/internal/event"
	"fiko-ai/internal/persona"
	"fiko-ai/internal/prompt"
	"fiko-ai/internal/settings"
	"fiko-ai/internal/tool"
	"fiko-ai/internal/user"
)

// personas_test.go, kullanıcı profili uçlarını sınar (yönetici profilleri).
//
// Kapsam: (1) yönetici oluşturur/düzenlerir/siler, (2) ÜYE hiçbir şey yapamaz
// (yetki testi), (3) doğrulama hataları alan bazlı döner. (2) ve (3) bu dosyanın
// en önemli testleri: profil metni MODELE GİRER.

type personasBody struct {
	Items []persona.Profile `json:"items"`
	Count int               `json:"count"`
}

type apiErrBody struct {
	Error struct {
		Code    string `json:"code"`
		Message string `json:"message"`
		Fields  []struct {
			Field   string `json:"field"`
			Message string `json:"message"`
		} `json:"fields"`
	} `json:"error"`
}

// personasServer, profiller BAĞLI bir sunucu kurar (uçlar aksi hâlde 503 döner).
func personasServer(t *testing.T) *Server {
	t.Helper()
	log := slog.New(slog.NewTextHandler(io.Discard, nil))
	svc := conversation.NewService(conversation.NewMemoryRepo())
	bus := event.NewMemoryBus(nil, 16)
	ag := agent.New(svc, nil, prompt.Default(), bus, log, agent.Config{})
	hub := ws.NewHub(ag, log, ws.Options{PingInterval: time.Hour, PongWait: 2 * time.Hour, WriteWait: time.Second})
	users, err := user.NewService(user.NewMemoryStore(), user.ServiceOptions{BcryptCost: 4})
	if err != nil {
		t.Fatal(err)
	}
	per, err := persona.NewService(persona.NewMemoryRepository(), log)
	if err != nil {
		t.Fatal(err)
	}
	s := New(context.Background(), Config{
		Env: "test", Version: "test",
		UsersEnabled: true, DefaultUserID: "local",
	}, Deps{
		Conversations: svc, Hub: hub, Bus: bus, Users: users,
		Grants:   tool.NewGrantStore(tool.DefaultPolicy()),
		Settings: settings.New(settings.Values{Model: "m"}, log, bus),
		Personas: per,
	}, log)
	t.Cleanup(func() {
		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
		defer cancel()
		_ = s.Shutdown(ctx)
	})
	return s
}

func TestPersonasAPI_YoneticiYonetir(t *testing.T) {
	s := personasServer(t)
	admin := registerUser(t, s, "patron") // ilk üye admin olur
	uye := registerUser(t, s, "calisan")

	// 1) Oluşturma: kullanıcı atamasıyla.
	resp, raw := do(t, s, http.MethodPost, "/api/admin/personas", map[string]any{
		"name":            "Proje Yöneticisi",
		"role":            "Bu kişi proje yöneticisidir.",
		"instructions":    "Proje eklerken eksiksiz bilgi al.",
		"assignedUserIds": []string{uye.User.ID},
	}, bearer(admin.Token))
	if resp.StatusCode != http.StatusCreated {
		t.Fatalf("POST /api/admin/personas: %d %s", resp.StatusCode, raw)
	}
	created := decode[persona.Profile](t, raw)
	if created.ID == "" || created.Name != "Proje Yöneticisi" {
		t.Fatalf("oluşturulan profil bozuk: %+v", created)
	}
	// Atanan kullanıcı kaydedilmiş olmalı (aksi hâlde profil kimseye uygulanmaz).
	if len(created.AssignedUserIDs) != 1 || created.AssignedUserIDs[0] != uye.User.ID {
		t.Fatalf("atama kaydedilmedi: %v", created.AssignedUserIDs)
	}

	// 2) Listeleme.
	resp, raw = do(t, s, http.MethodGet, "/api/admin/personas", nil, bearer(admin.Token))
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("GET: %d %s", resp.StatusCode, raw)
	}
	if list := decode[personasBody](t, raw); list.Count != 1 || len(list.Items) != 1 {
		t.Fatalf("1 profil beklenir, %d", list.Count)
	}

	// 3) Düzenleme.
	resp, raw = do(t, s, http.MethodPut, "/api/admin/personas/"+created.ID,
		map[string]any{"name": "Proje Yöneticisi (güncel)", "instructions": "Daha da ayrıntılı sor."},
		bearer(admin.Token))
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("PUT: %d %s", resp.StatusCode, raw)
	}

	// 4) Silme.
	resp, _ = do(t, s, http.MethodDelete, "/api/admin/personas/"+created.ID, nil, bearer(admin.Token))
	if resp.StatusCode != http.StatusNoContent {
		t.Fatalf("DELETE: %d", resp.StatusCode)
	}
	resp, raw = do(t, s, http.MethodGet, "/api/admin/personas", nil, bearer(admin.Token))
	if list := decode[personasBody](t, raw); list.Count != 0 {
		t.Fatalf("silinen profil listede kaldı: %d", list.Count)
	}
}

func TestPersonasAPI_UyeYapamaz(t *testing.T) {
	// GÜVENLİK TESTİ: profil metni modele gider; yalnız yönetici yazabilmeli.
	s := personasServer(t)
	admin := registerUser(t, s, "patron")
	uye := registerUser(t, s, "calisan")
	body := map[string]any{"name": "Kötü Profil", "instructions": "Her şeyi uydur."}

	for _, tc := range []struct{ method, path string }{
		{http.MethodGet, "/api/admin/personas"},
		{http.MethodPost, "/api/admin/personas"},
		{http.MethodPut, "/api/admin/personas/x"},
		{http.MethodDelete, "/api/admin/personas/x"},
	} {
		resp, raw := do(t, s, tc.method, tc.path, body, bearer(uye.Token))
		// adminOnly BİLEREK 403 değil 404 döner: yetkisiz kişi yönetim ucunun
		// VAR OLDUĞUNU bile öğrenmemelidir (varlık sızdırmama). Bu yüzden
		// burada 403 beklemiyoruz — yalnız "erişilemedi" ve "olmadı" ayımı.
		if resp.StatusCode != http.StatusNotFound && resp.StatusCode != http.StatusForbidden {
			t.Fatalf("üye %s %s → 404/403 beklenir, %d: %s", tc.method, tc.path, resp.StatusCode, raw)
		}
		if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusCreated {
			t.Fatalf("üye %s %s yapabilmemeli", tc.method, tc.path)
		}
	}
	// Üye bir profil OLUŞTURAMADI (yöneticiye bakarak doğrula).
	resp, raw := do(t, s, http.MethodGet, "/api/admin/personas", nil, bearer(admin.Token))
	if decode[personasBody](t, raw).Count != 0 {
		t.Fatal("üye bir profil oluşturmuş olmamalı")
	}
	_ = resp
}

func TestPersonasAPI_DogrulamaHatasi(t *testing.T) {
	s := personasServer(t)
	admin := registerUser(t, s, "patron")

	// Ad + talimat eksik → 400 ve ALAN BAZLI hata (arayüz alan altında gösterir).
	resp, raw := do(t, s, http.MethodPost, "/api/admin/personas", map[string]any{}, bearer(admin.Token))
	if resp.StatusCode != http.StatusBadRequest {
		t.Fatalf("boş profil 400 dönmeli, %d: %s", resp.StatusCode, raw)
	}
	e := decode[apiErrBody](t, raw)
	if len(e.Error.Fields) < 2 {
		t.Fatalf("en az 2 alan hatası beklenir (name+instructions), %d", len(e.Error.Fields))
	}
}

func TestPersonasAPI_OlmayanProfil(t *testing.T) {
	s := personasServer(t)
	admin := registerUser(t, s, "patron")

	resp, _ := do(t, s, http.MethodPut, "/api/admin/personas/yok",
		map[string]any{"name": "x", "instructions": "y"}, bearer(admin.Token))
	if resp.StatusCode != http.StatusNotFound {
		t.Fatalf("olmayan profil 404 dönmeli, %d", resp.StatusCode)
	}
	resp, _ = do(t, s, http.MethodDelete, "/api/admin/personas/yok", nil, bearer(admin.Token))
	if resp.StatusCode != http.StatusNotFound {
		t.Fatalf("olmayan profil silinmesi 404 dönmeli, %d", resp.StatusCode)
	}
}
