package api

import (
	"errors"
	"strings"

	"github.com/gofiber/fiber/v2"

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

// Kullanıcılar arası bildirim uçları: gelen kutusu, gönendiler, okundu işaretleme,
// yanıtlama ve silme.
//
// GİZLİLİK: bildirimler ALICIYA AİTTİR; her uç s.currentUser(c) ile çalışır.
// Başkasının bildirimi 404'tür (var olduğunu bile söylemeyiz).
//
// Neden hem REST hem WS? WS anlıktır (bildirim gider), REST ise KALICI kaynağı
// okur: sayfa yenilendiğinde/çevrimdışıyken kaçan bildirim ancak buradan gelir.
// İkisi birbirinin yerine geçmez.
type notificationsResponse struct {
	Items []notify.Message `json:"items"`
	Count int              `json:"count"`
	Total int              `json:"total"`
	// Unread zil sayısıdır; Pending yanıt bekleyen soru sayısı. Arayüz tek
	// istekte hem listeyi hem rozetleri alır (ayrı uç = ayrı gecikme).
	Unread  int `json:"unread"`
	Pending int `json:"pending"`
	Limit   int `json:"limit"`
	Offset  int `json:"offset"`
}

type notifyAnswerRequest struct {
	Answer string `json:"answer"`
}

// notifyAvailable bildirim özelliği kapalıysa (üyelik/kurulum yoksa) 503 döner.
func (s *Server) notifyAvailable(c *fiber.Ctx) error {
	if s.deps.Notify == nil {
		return fail(c, fiber.StatusServiceUnavailable, protocol.ErrCodeInternal,
			"bildirim özelliği yapılandırılmamış")
	}
	return c.Next()
}

// listNotifications GET /api/notifications?box=inbox|sent&pending=true&limit=&offset=
func (s *Server) listNotifications(c *fiber.Ctx) error {
	limit, offset, err := s.parsePaging(c)
	if err != nil {
		return err
	}
	userID := s.currentUser(c)

	box := strings.TrimSpace(c.Query("box"))
	if box == "" {
		box = notify.BoxInbox // varsayılan: gelen kutusu
	}
	if box != notify.BoxInbox && box != notify.BoxSent {
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest,
			"box yalnızca inbox veya sent olabilir")
	}
	// pending=true yalnızca inbox'ta anlamlıdır: arayüz "yanıt bekleyen soru"ları
	// oradan listeler.
	pendingOnly := c.Query("pending") == "true"

	items, total, err := s.deps.Notify.List(c.UserContext(), userID, notify.Filter{
		Box: box, PendingOnly: pendingOnly, Limit: limit, Offset: offset,
	})
	if err != nil {
		return failNotify(c, err)
	}
	if items == nil {
		items = []notify.Message{} // JSON'da null değil []
	}
	return c.JSON(notificationsResponse{
		Items: items, Count: len(items), Total: total,
		Unread:  s.deps.Notify.Unread(c.UserContext(), userID),
		Pending: len(s.deps.Notify.PendingQuestions(userID)),
		Limit:   limit, Offset: offset,
	})
}

// getNotification GET /api/notifications/:id — tek bildirim.
//
// Katılım: yalnızca ALICI veya GÖNDEREN okuyabilir; başkasınınki 404'tür.
// strings.Clone KULLANILIR: Fiber'in c.Params() değeri havuzdan gelir ve handler
// bitince bozulur (bkz. ILERLEME.md notu).
func (s *Server) getNotification(c *fiber.Ctx) error {
	userID, id := s.currentUser(c), strings.Clone(c.Params("id"))
	m, err := s.deps.Notify.Repository().Get(c.UserContext(), id)
	if err != nil {
		return failNotify(c, err)
	}
	if m.ToUserID != userID && m.FromUserID != userID {
		return fail(c, fiber.StatusNotFound, protocol.ErrCodeNotFound, "bulunamadı")
	}
	return c.JSON(m)
}

// readNotification POST /api/notifications/:id/read — okundu işaretle.
//
// Neden POST, PUT değil? Gövdesiz ve idempotent; proje de onay akışında aynı
// tercihi kullanır (bkz. memory.answer). Yanıtta GÜNCEL sayaçlar döner; arayüz
// rozeti ayrı istek yapmadan tazeler.
func (s *Server) readNotification(c *fiber.Ctx) error {
	userID, id := s.currentUser(c), strings.Clone(c.Params("id"))
	if _, err := s.deps.Notify.MarkRead(c.UserContext(), userID, id); err != nil {
		s.auditFailure(userID, "notification.read", id+": "+err.Error())
		return failNotify(c, err)
	}
	s.auditSuccess(userID, "notification.read", id)
	return c.JSON(notificationsResponse{
		Unread:  s.deps.Notify.Unread(c.UserContext(), userID),
		Pending: len(s.deps.Notify.PendingQuestions(userID)),
	})
}

// answerNotification POST /api/notifications/:id/answer — soruya yanıt ver.
//
// Bu, WS `notify.answer` olayının REST karşılığıdır: ikisi de AYNI servisi
// çağırır, yani katılım denetimi (yalnızca alıcı) ve yarış koruması (tek
// yazım) aynı yerden geçer. İkinci bir yol olması güvenlik açığı DEĞİLDİR.
func (s *Server) answerNotification(c *fiber.Ctx) error {
	var in notifyAnswerRequest
	if err := c.BodyParser(&in); err != nil {
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, "geçersiz istek gövdesi")
	}
	userID, id := s.currentUser(c), strings.Clone(c.Params("id"))
	if strings.TrimSpace(in.Answer) == "" {
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, "yanıt boş olamaz")
	}
	_, reply, err := s.deps.Notify.Answer(c.UserContext(), userID, id, in.Answer)
	if err != nil {
		s.auditFailure(userID, "notification.answer", id+": "+err.Error())
		return failNotify(c, err)
	}
	s.auditSuccess(userID, "notification.answer", id)
	// reply döner: arayüz "gönendiler" listesine anında ekleyebilir (WS düşse bile).
	return c.JSON(notificationsResponse{
		Items: []notify.Message{reply}, Count: 1, Total: 1,
		Unread:  s.deps.Notify.Unread(c.UserContext(), userID),
		Pending: len(s.deps.Notify.PendingQuestions(userID)),
	})
}

// deleteNotification DELETE /api/notifications/:id — bildirimi sil.
func (s *Server) deleteNotification(c *fiber.Ctx) error {
	userID, id := s.currentUser(c), strings.Clone(c.Params("id"))
	if err := s.deps.Notify.Delete(c.UserContext(), userID, id); err != nil {
		s.auditFailure(userID, "notification.delete", id+": "+err.Error())
		return failNotify(c, err)
	}
	s.auditSuccess(userID, "notification.delete", id)
	return c.SendStatus(fiber.StatusNoContent)
}

// failNotify, notify hatalarını HTTP durumuna çevirir.
//
// AYRIM: ErrForbidden ve ErrNotFound ikisi de 404 döner — "başkasının sorun var"
// demek zaten BİLGİ SIZDIRIR (bellek sorularındaki "bulunamadı" ayrımının aynı
// dersi). Denetim kaydı ikisini AYRI yazar, kullanıcı görmez.
func failNotify(c *fiber.Ctx, err error) error {
	switch {
	case errors.Is(err, notify.ErrNotFound), errors.Is(err, notify.ErrForbidden):
		return fail(c, fiber.StatusNotFound, protocol.ErrCodeNotFound, "bulunamadı")
	case errors.Is(err, notify.ErrValidation):
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, err.Error())
	case errors.Is(err, notify.ErrLimit):
		return fail(c, fiber.StatusTooManyRequests, protocol.ErrCodeBadRequest, err.Error())
	default:
		return fail(c, fiber.StatusInternalServerError, protocol.ErrCodeInternal,
			"bildirim işlemi başarısız")
	}
}
