package api

// session.go oturum (üyelik) kimliğinin yardımcılarını toplar: açık uçlar, rol denetimi ve
// token'ın nereden okunacağı. middleware.go'daki çözümleme akışı bunları kullanır.

import (
	"errors"
	"strings"

	"github.com/gofiber/fiber/v2"

	"fiko-ai/internal/protocol"
	"fiko-ai/internal/user"
)

// localsUserRole Fiber Locals içinde oturumdaki rolün saklandığı anahtardır ("admin" | "user").
const localsUserRole = "fiko.userRole"

// localsViaSession, kimliğin GEÇERLİ BİR OTURUM token'ından geldiğini işaretler.
// authorize bunu görürse paylaşılan API anahtarını ayrıca istemez.
const localsViaSession = "fiko.viaSession"

// roleAdmin, paylaşılan API anahtarıyla gelen isteğin rolüdür. FIKO_API_KEY işletmecinin
// sırrıdır; onu bilen zaten sunucunun sahibidir.
const roleAdmin = string(user.RoleAdmin)

var (
	// errLoginRequired: üyelik açık ama istekte oturum yok → 401.
	errLoginRequired = errors.New("giriş gerekli")
	// errBadSession: token var ama geçersiz/süresi dolmuş → 401.
	errBadSession = errors.New("oturum geçersiz veya süresi dolmuş")
)

// authFailStatus kimlik çözümleme hatasını HTTP durumuna çevirir: oturum sorunları 401,
// geri kalanı (bozuk başlık gibi) 400.
func authFailStatus(err error) (int, string) {
	if errors.Is(err, errLoginRequired) || errors.Is(err, errBadSession) {
		return fiber.StatusUnauthorized, protocol.ErrCodeUnauthorized
	}
	return fiber.StatusBadRequest, protocol.ErrCodeBadRequest
}

// isPublicRequest, kimlik İSTEMEYEN istekleri söyler.
//
// Neden gerekli? Üyelik açıkken genel `resolveUser` her isteği reddederdi: ön yüz
// `/api/system`'e bakıp "giriş gerekli mi" diye öğrenemez, `/api/health` izleme
// sondası düşer, CORS ön kontrolü (OPTIONS) tarayıcıda başlık taşıyamadığı için
// engellenirdi. Bu yollar veri sızdırmaz; yalnızca yapılandırma/sağlık bilgisi verir.
func isPublicRequest(c *fiber.Ctx) bool {
	if c.Method() == fiber.MethodOptions {
		return true
	}
	p := strings.TrimRight(c.Path(), "/")
	switch p {
	case "/api/health", "/api/models", "/api/system":
		return true
	}
	return isAuthPath(c)
}

// viaSession, kimliğin oturum token'ından çözülüp çözülmediğini söyler.
func viaSession(c *fiber.Ctx) bool {
	v, _ := c.Locals(localsViaSession).(bool)
	return v
}

// sessionUser, istekteki token'ı doğrular ve kullanıcıyı döner (token yoksa/geçersizse ok=false).
func (s *Server) sessionUser(c *fiber.Ctx) (*user.User, bool) {
	if s.deps.Users == nil {
		return nil, false
	}
	tok := bearerToken(c)
	if tok == "" {
		return nil, false
	}
	u, err := s.deps.Users.Validate(c.Context(), tok)
	if err != nil {
		return nil, false
	}
	return u, true
}

// isAdmin, istek sahibinin yönetici olup olmadığını söyler.
//
// Üyelik KAPALIYKEN rol kavramı yoktur (tek kullanıcılı geliştirme akışı) → herkes yönetici
// sayılır; böylece eski akış bozulmaz. Üyelik AÇIKKEN yalnızca rolü "admin" olanlar
// (veya paylaşılan API anahtarını bilenler) yöneticidir.
func (s *Server) isAdmin(c *fiber.Ctx) bool {
	if !s.cfg.UsersEnabled {
		return true
	}
	r, _ := c.Locals(localsUserRole).(string)
	return r == roleAdmin
}

// adminOnly yönetici olmayanlar için ucu GÖRÜNMEZ yapar (403 değil 404): yetkisiz kişi
// böyle bir yönetim ucunun var olduğunu bile öğrenmez.
func (s *Server) adminOnly(c *fiber.Ctx) error {
	if !s.isAdmin(c) {
		return fail(c, fiber.StatusNotFound, protocol.ErrCodeNotFound, "bulunamadı")
	}
	return c.Next()
}
