package api

import (
	"errors"
	"strings"
	"time"

	"github.com/gofiber/fiber/v2"

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

// AuthHeaderBearer, tarayıcının gönderdiği yetkilendirme başlığıdır.
// RFC 6750: "Authorization: Bearer <token>"
const AuthHeaderBearer = "Authorization"

// TokenCookie, tarayıcının otomatik gönderdiği çerez adıdır.
//
// Neden çerez de var? WebSocket bağlantısında tarayıcı ÖZEL BAŞLIK GÖNDEREMEZ.
// Çerez her istekte (WebSocket dahil) otomatik gider. Bu yüzden gerçek kullanıcı
// sisteminde oturum çerezi pratikte zorunludur.
const TokenCookie = "fiko_session"

// hesapKullanilabilir: user servisi bağlı mı?
func (s *Server) hesapKullanilabilir(c *fiber.Ctx) error {
	if s.deps.Users == nil {
		return fail(c, fiber.StatusServiceUnavailable, protocol.ErrCodeInternal,
			"hesap sistemi kapalı (MONGO_URI tanımlı değil)")
	}
	return c.Next()
}

// registerRequest, yeni hesap isteğidir.
type registerRequest struct {
	Username    string `json:"username"`
	Password    string `json:"password"`
	DisplayName string `json:"displayName"`
}

// loginRequest, giriş isteğidir.
type loginRequest struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

// authResponse, giriş/kayıt sonrası dönen gövdedir.
//
// Token burada bir kez düz metin olarak verilir; sunucu yalnızca özetini tutar.
type authResponse struct {
	Token     string        `json:"token"`
	ExpiresAt string        `json:"expiresAt"`
	User      user.UserView `json:"user"`
}

// register POST /api/auth/register — yeni üyelik açar.
//
// AÇIK uçtur (kimlik doğrulaması gerektirmez, aksi hâlde kimse üye olamaz).
// Ama bu, "herkes üye olabilir" demektir: üyelik sistemi KAPALIYKEN bu uç 404 döner.
func (s *Server) registerUser(c *fiber.Ctx) error {
	if s.cfg.UsersEnabled == false {
		return fail(c, fiber.StatusNotFound, protocol.ErrCodeNotFound,
			"üyelik sistemi kapalı (FIKO_USERS_ENABLED=true yap)")
	}
	var req registerRequest
	if err := parseBody(c, &req); err != nil {
		return badRequest(c, err)
	}
	req.Username = strings.TrimSpace(req.Username)

	u, err := s.deps.Users.Register(c.Context(), req.Username, req.Password, req.DisplayName)
	if err != nil {
		// Kullanıcı adı alınmışsa 409; parola/ad kuralları 400.
		if errors.Is(err, user.ErrExists) {
			return fail(c, fiber.StatusConflict, protocol.ErrCodeBadRequest, err.Error())
		}
		return badRequest(c, err)
	}
	// Kayıt OLUR OLMAZ oturum açılır: kullanıcı hemen kullanmaya başlar.
	_, token, expires, err := s.deps.Users.Login(c.Context(), req.Username, req.Password)
	if err != nil {
		return badRequest(c, err)
	}
	return s.authOK(c, token, expires, u)
}

// login POST /api/auth/login — giriş yapar.
func (s *Server) loginUser(c *fiber.Ctx) error {
	if s.cfg.UsersEnabled == false {
		return fail(c, fiber.StatusNotFound, protocol.ErrCodeNotFound, "üyelik sistemi kapalı")
	}
	var req loginRequest
	if err := parseBody(c, &req); err != nil {
		return badRequest(c, err)
	}

	u, token, expires, err := s.deps.Users.Login(c.Context(), req.Username, req.Password)
	if err != nil {
		// Bilerek TEK mesaj: "kullanıcı yok" demek hesapları sızdırır.
		if errors.Is(err, user.ErrDisabled) {
			return fail(c, fiber.StatusForbidden, protocol.ErrCodeUnauthorized, "hesap devre dışı")
		}
		return fail(c, fiber.StatusUnauthorized, protocol.ErrCodeUnauthorized, err.Error())
	}
	return s.authOK(c, token, expires, u)
}

// logout POST /api/auth/logout — oturumu kapatır.
func (s *Server) logoutUser(c *fiber.Ctx) error {
	if s.deps.Users == nil {
		return c.JSON(fiber.Map{"ok": true})
	}
	if tok := bearerToken(c); tok != "" {
		s.deps.Users.Logout(tok)
	}
	c.Cookie(&fiber.Cookie{
		Name:     TokenCookie,
		Value:    "",
		Path:     "/",
		Expires:  timePast(),
		MaxAge:   -1,
		HTTPOnly: true, // JavaScript okuyamaz: XSS ile çalınamaz
		SameSite: "Lax",
	})
	return c.JSON(fiber.Map{"ok": true})
}

// me GET /api/auth/me — oturumdaki kullanıcıyı döner.
//
// Arayüz açılışta bunu çağırır: "geçerli oturum var mı" sorusunun tek yanıtı.
func (s *Server) meUser(c *fiber.Ctx) error {
	if s.deps.Users == nil {
		return fail(c, fiber.StatusServiceUnavailable, protocol.ErrCodeInternal, "hesap sistemi kapalı")
	}
	uid, _ := c.Locals(localsUserID).(string)
	if uid == "" {
		return fail(c, fiber.StatusUnauthorized, protocol.ErrCodeUnauthorized, "oturum yok")
	}
	u, err := s.deps.Users.ByID(c.Context(), uid)
	if err != nil {
		return fail(c, fiber.StatusUnauthorized, protocol.ErrCodeUnauthorized, "oturum geçersiz")
	}
	return c.JSON(fiber.Map{
		"user": u.View(),
		"role": u.Role,
	})
}

// authOK, başarılı giriş yanıtını üretir ve çerezi ayarlar.
func (s *Server) authOK(c *fiber.Ctx, token string, expires time.Time, u *user.User) error {
	c.Cookie(&fiber.Cookie{
		Name:     TokenCookie,
		Value:    token,
		Path:     "/",
		Expires:  expires,
		HTTPOnly: true,                // JavaScript erişemez (XSS ile çalınamaz)
		SameSite: "Lax",               // siteler arası gönderim yok (CSRF azaltma)
		Secure:   s.cfg.AuthKey != "", // anahtar zorunluysa HTTPS zorunlu olsun
	})
	return c.JSON(authResponse{
		Token:     token,
		ExpiresAt: expires.UTC().Format(timeFormat),
		User:      u.View(),
	})
}

// timeFormat, API'de kullanılan zaman biçimidir.
const timeFormat = time.RFC3339

// timePast, çerezi silmek için "geçmişte" bir zaman üretir.
func timePast() time.Time { return time.Now().Add(-time.Hour) }

// parseBody, JSON gövdeyi hedefe çözer.
func parseBody(c *fiber.Ctx, out any) error {
	if err := c.BodyParser(out); err != nil {
		return errBadBody
	}
	return nil
}

// errBadBody, bozuk JSON gövdesini temsil eder.
var errBadBody = errors.New("geçersiz istek gövdesi")

// badRequest, 400 döner.
func badRequest(c *fiber.Ctx, err error) error {
	return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, err.Error())
}

// bearerToken, istekten oturum token'ını çıkarır.
//
// Öncelik SIRA: Authorization başlığı > çerez. Başlık, script'in (fetch) kullandığı
// yoldur; çerez ise tarayıcının WebSocket dahil OTOMATİK gönderdiği yoldur.
func bearerToken(c *fiber.Ctx) string {
	if h := strings.TrimSpace(c.Get(AuthHeaderBearer)); h != "" {
		// "Bearer <token>" biçimini kabul et; scheme yoksa ham değeri kullan.
		if len(h) > 7 && strings.EqualFold(h[:7], "Bearer ") {
			return strings.TrimSpace(h[7:])
		}
		return h
	}
	if ck := strings.TrimSpace(c.Cookies(TokenCookie)); ck != "" {
		return ck
	}
	// Son çare: YALNIZCA WebSocket yolunda ?token= . Tarayıcı WebSocket'i özel başlık
	// gönderemez; çerez ise ng serve proxy'si/ farklı origin'de taşınmayabilir. REST'te URL'ye
	// token koymak erişim günlüklerine sızdıracağından oralarda KABUL EDILMEZ.
	if c.Path() == WSPath {
		return strings.TrimSpace(c.Query("token"))
	}
	return ""
}

// usersEnabled, üyelik sistemi kapalıyken 404 döner.
//
// Amaç: üyelik kapatıldığında uçlar GÖRÜNMEZ (404), "düşük alçaklı" 403 değil —
// istemci özellik olmadığını anlasın.
func (s *Server) usersEnabled(c *fiber.Ctx) error {
	if !s.cfg.UsersEnabled || s.deps.Users == nil {
		return fail(c, fiber.StatusNotFound, protocol.ErrCodeNotFound,
			"üyelik sistemi kapalı (FIKO_USERS_ENABLED=true yap)")
	}
	return c.Next()
}
