package api

import (
	"errors"
	"strconv"
	"strings"

	"github.com/gofiber/fiber/v2"

	"fiko-ai/internal/llm"
	"fiko-ai/internal/memory"
	"fiko-ai/internal/protocol"
)

// Kalıcı bellek (Faz 10) uçları: asistanın kullanıcı hakkında hatırladıkları.
// Anılar kullanıcıya aittir: her uç s.currentUser(c) ile çalışır, başkasının anısı 404'tür.
//
// GİZLİLİK: anı içeriği kişisel veridir; denetim kaydına ve loglara YAZILMAZ, yalnızca kimlik ve sayılar.

type memoryResponse struct {
	Memory  memory.Memory  `json:"memory"`
	Outcome memory.Outcome `json:"outcome,omitempty"` // yalnızca ekleme: created | updated | unchanged
}

type memoriesResponse struct {
	Items  []memory.Memory `json:"items"`
	Count  int             `json:"count"`
	Total  int             `json:"total"` // süzgeçsiz toplam (kullanıcının tüm anıları)
	Limit  int             `json:"limit"`
	Offset int             `json:"offset"`
}

type memoryCreateRequest struct {
	Kind       memory.Kind `json:"kind"` // boş = long_term
	Content    string      `json:"content"`
	Tags       []string    `json:"tags"`
	Importance float64     `json:"importance"` // 0 = varsayılan (0.8)
}

// memoryUpdateRequest kısmi güncellemedir: gönderilmeyen (null) alanlar değişmez.
type memoryUpdateRequest struct {
	Content    *string      `json:"content"`
	Kind       *memory.Kind `json:"kind"`
	Tags       *[]string    `json:"tags"`
	Importance *float64     `json:"importance"`
}

type memorySearchRequest struct {
	Query    string  `json:"query"`
	Limit    int     `json:"limit"`    // boş = 5, en fazla 50
	MinScore float64 `json:"minScore"` // 0..1
}

type memorySearchResponse struct {
	Items []memory.Hit `json:"items"` // skora göre azalan
	Count int          `json:"count"`
}

// memoryAvailable bellek yapılandırılmamışsa (MEMORY_ENABLED=false) tüm /api/memories uçlarını 503 yapar.
func (s *Server) memoryAvailable(c *fiber.Ctx) error {
	if s.deps.Memory == nil {
		return fail(c, fiber.StatusServiceUnavailable, protocol.ErrCodeInternal,
			"kalıcı bellek yapılandırılmamış (MEMORY_ENABLED=false olabilir)")
	}
	return c.Next()
}

// listMemories GET /api/memories?kind=&tag=&q=&limit=&offset=
func (s *Server) listMemories(c *fiber.Ctx) error {
	limit, offset, err := s.parsePaging(c)
	if err != nil {
		return err
	}
	userID := s.currentUser(c)
	f := memory.Filter{
		UserID: userID,
		Kind:   memory.Kind(strings.TrimSpace(c.Query("kind"))),
		Tag:    strings.Clone(c.Query("tag")),
		Query:  strings.Clone(c.Query("q")),
	}
	items, err := s.deps.Memory.List(c.UserContext(), f, limit, offset)
	if err != nil {
		return failMemory(c, err)
	}
	total, err := s.deps.Memory.Count(c.UserContext(), userID)
	if err != nil {
		return failMemory(c, err)
	}
	if items == nil {
		items = []memory.Memory{} // JSON'da null değil []
	}
	return c.JSON(memoriesResponse{Items: items, Count: len(items), Total: total, Limit: limit, Offset: offset})
}

// getMemory GET /api/memories/:id
func (s *Server) getMemory(c *fiber.Ctx) error {
	m, err := s.deps.Memory.Get(c.UserContext(), s.currentUser(c), strings.Clone(c.Params("id")))
	if err != nil {
		return failMemory(c, err)
	}
	return c.JSON(memoryResponse{Memory: *m})
}

// createMemory POST /api/memories — kullanıcının elle eklediği anı (origin=user).
//
// Aynı bilgi zaten varsa tekrar edilmez (200, outcome=unchanged); aynı konuda eski bir anı varsa
// yenisi onun yerini alır (200, outcome=updated). Yeni anı 201 döner.
func (s *Server) createMemory(c *fiber.Ctx) error {
	var in memoryCreateRequest
	if err := c.BodyParser(&in); err != nil {
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, "geçersiz istek gövdesi")
	}
	userID := s.currentUser(c)
	m, outcome, err := s.deps.Memory.Add(c.UserContext(), memory.AddInput{
		UserID: userID, Kind: in.Kind, Content: in.Content, Tags: in.Tags,
		Origin: memory.OriginUser, Importance: in.Importance,
	})
	if err != nil {
		s.auditFailure(userID, "memory.create", err.Error())
		return failMemory(c, err)
	}
	s.auditSuccess(userID, "memory.create", "id="+m.ID+" outcome="+string(outcome))
	status := fiber.StatusOK
	if outcome == memory.Created {
		status = fiber.StatusCreated
	}
	return c.Status(status).JSON(memoryResponse{Memory: *m, Outcome: outcome})
}

// updateMemory PUT /api/memories/:id — kısmi güncelleme. Elle düzenlenen anı kullanıcıya ait olur
// (otomatik çıkarım onu ezmez).
func (s *Server) updateMemory(c *fiber.Ctx) error {
	var in memoryUpdateRequest
	if err := c.BodyParser(&in); err != nil {
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, "geçersiz istek gövdesi")
	}
	if in.Content == nil && in.Kind == nil && in.Tags == nil && in.Importance == nil {
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest,
			"güncellenecek en az bir alan gerekli (content, kind, tags, importance)")
	}
	userID, id := s.currentUser(c), strings.Clone(c.Params("id"))
	m, err := s.deps.Memory.Update(c.UserContext(), memory.UpdateInput{
		UserID: userID, ID: id,
		Content: in.Content, Kind: in.Kind, Tags: in.Tags, Importance: in.Importance,
	})
	if err != nil {
		s.auditFailure(userID, "memory.update", id+": "+err.Error())
		return failMemory(c, err)
	}
	s.auditSuccess(userID, "memory.update", id)
	return c.JSON(memoryResponse{Memory: *m})
}

// deleteMemory DELETE /api/memories/:id
func (s *Server) deleteMemory(c *fiber.Ctx) error {
	userID, id := s.currentUser(c), strings.Clone(c.Params("id"))
	if err := s.deps.Memory.Delete(c.UserContext(), userID, id); err != nil {
		s.auditFailure(userID, "memory.delete", id+": "+err.Error())
		return failMemory(c, err)
	}
	s.auditSuccess(userID, "memory.delete", id)
	return c.SendStatus(fiber.StatusNoContent)
}

// deleteAllMemories DELETE /api/memories?confirm=true — "beni unut": kullanıcının TÜM anıları silinir.
// Geri alınamaz olduğu için açık onay (confirm=true) ister; onaysız istek 400'dür.
func (s *Server) deleteAllMemories(c *fiber.Ctx) error {
	if strings.TrimSpace(c.Query("confirm")) != "true" {
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest,
			"tüm anıları silmek için ?confirm=true gerekli (geri alınamaz)")
	}
	userID := s.currentUser(c)
	n, err := s.deps.Memory.DeleteAll(c.UserContext(), userID)
	if err != nil {
		s.auditFailure(userID, "memory.delete_all", err.Error())
		return failMemory(c, err)
	}
	s.auditSuccess(userID, "memory.delete_all", "count="+strconv.Itoa(n))
	return c.JSON(fiber.Map{"deleted": n})
}

// searchMemories POST /api/memories/search — hibrit (vektör + anahtar kelime) arama. Okuma işlemidir.
// Not: Recall'dan farklı olarak kullanım sayacını artırmaz (arayüzde arama yapmak "kullanım" değildir).
func (s *Server) searchMemories(c *fiber.Ctx) error {
	var in memorySearchRequest
	if err := c.BodyParser(&in); err != nil {
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, "geçersiz istek gövdesi")
	}
	hits, err := s.deps.Memory.Search(c.UserContext(), memory.SearchOptions{
		UserID: s.currentUser(c), Query: in.Query, Limit: in.Limit, MinScore: in.MinScore,
	})
	if err != nil {
		return failMemory(c, err)
	}
	if hits == nil {
		hits = []memory.Hit{}
	}
	return c.JSON(memorySearchResponse{Items: hits, Count: len(hits)})
}

// reindexMemories POST /api/memories/reindex[?force=true] — eksik/bayat vektörleri arka planda üretir
// (embedding modeli değişince). Hemen döner (202).
func (s *Server) reindexMemories(c *fiber.Ctx) error {
	force := false
	if raw := strings.TrimSpace(c.Query("force")); raw != "" {
		v, err := strconv.ParseBool(raw)
		if err != nil {
			return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, "force true veya false olmalı")
		}
		force = v
	}
	userID := s.currentUser(c)
	rep, err := s.deps.Memory.Reindex(c.UserContext(), userID, force)
	if err != nil {
		s.auditFailure(userID, "memory.reindex", err.Error())
		return failMemory(c, err)
	}
	s.auditSuccess(userID, "memory.reindex", "force="+strconv.FormatBool(force)+" queued="+strconv.Itoa(rep.Queued))
	return c.Status(fiber.StatusAccepted).JSON(rep)
}

// failMemory bellek hatalarını HTTP durumuna çevirir; kalanı failDomain'e bırakır.
func failMemory(c *fiber.Ctx, err error) error {
	var ve *memory.ValidationError
	var pe *llm.ProviderError
	switch {
	case errors.As(err, &ve):
		fields := make([]fieldError, 0, len(ve.Errors))
		for _, e := range ve.Errors {
			fields = append(fields, fieldError{Field: e.Field, Message: e.Message})
		}
		return c.Status(fiber.StatusBadRequest).JSON(errorBody{Error: apiError{
			Code: protocol.ErrCodeValidation, Message: "anı geçersiz", Fields: fields,
		}})
	case errors.Is(err, memory.ErrNotFound):
		return fail(c, fiber.StatusNotFound, protocol.ErrCodeNotFound, "anı bulunamadı")
	case errors.Is(err, memory.ErrDuplicate):
		return fail(c, fiber.StatusConflict, protocol.ErrCodeConflict, "aynı anı zaten var")
	case errors.Is(err, memory.ErrLimit):
		return fail(c, fiber.StatusConflict, protocol.ErrCodeConflict,
			"anı sınırına ulaşıldı; yenisini eklemek için eskilerden birini sil")
	case errors.Is(err, memory.ErrNoEmbedder), errors.Is(err, llm.ErrNoEmbedModel):
		return fail(c, fiber.StatusServiceUnavailable, protocol.ErrCodeLLM,
			"embedding sağlayıcısı yapılandırılmamış (OLLAMA_EMBED_MODEL ayarla)")
	case errors.As(err, &pe):
		return failLLM(c, err)
	default:
		return failDomain(c, err)
	}
}
