package api

import (
	"errors"
	"strings"

	"github.com/gofiber/fiber/v2"

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

// Ekip ortak kayıtları (iddia hafızası) — görüntüleme, düzenleme, silme, geçmiş (Faz 5).
//
// KİMLİK: yetki YALNIZCA doğrulanmış hesap kimliğiyle verilir (claim.Identity). Üyelik kapalıyken,
// hesabı olmayan ya da paylaşılan API anahtarının varsayılan kimliğiyle gelen istek reddedilir;
// X-Fiko-User başlığına güvenilmez. Kimlik yol/gövde/sorgudan DEĞİL oturumdan alınır.
//
// YETKİ: düzenleme/silme/geçmiş → kaydın yazarı ya da patron. Listeleme ve tek kayıt görüntüleme
// her doğrulanmış üyeye açıktır (kayıtlar ekip ortağıdır; tür bazlı görünürlük Faz 8).

// claimsAvailable servis kurulu değilse 503 döner.
func (s *Server) claimsAvailable(c *fiber.Ctx) error {
	if s.deps.Claims == nil || s.deps.ClaimIdentity == nil {
		return fail(c, fiber.StatusServiceUnavailable, protocol.ErrCodeInternal, "ekip kayıtları yapılandırılmamış")
	}
	return c.Next()
}

// claimActor oturumdaki kullanıcıyı doğrulanmış Actor'a çevirir.
func (s *Server) claimActor(c *fiber.Ctx) (claim.Actor, error) {
	return s.deps.ClaimIdentity.Actor(c.UserContext(), s.currentUser(c))
}

// failClaim claim paketi hatalarını HTTP durumuna çevirir.
func failClaim(c *fiber.Ctx, err error) error {
	var ve *claim.ValidationError
	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: "kayıt geçersiz", Fields: fields,
		}})
	case errors.Is(err, claim.ErrNotFound):
		return fail(c, fiber.StatusNotFound, protocol.ErrCodeNotFound, "kayıt bulunamadı")
	case errors.Is(err, claim.ErrForbidden):
		return fail(c, fiber.StatusForbidden, protocol.ErrCodePermissionDenied,
			"bu kaydı yalnızca yazarı ya da patron değiştirebilir")
	case errors.Is(err, claim.ErrKeyTaken):
		return fail(c, fiber.StatusConflict, protocol.ErrCodeConflict, "aynı türde bu ada sahip başka bir kayıt var")
	case errors.Is(err, claim.ErrPatronOnly):
		return fail(c, fiber.StatusForbidden, protocol.ErrCodePermissionDenied,
			"bu işlemi yalnızca patron yapabilir (çelişki kararı, ilgili kişi atama)")
	case errors.Is(err, claim.ErrNotInConflict):
		return fail(c, fiber.StatusConflict, protocol.ErrCodeConflict, "kayıt çelişkili durumda değil")
	case errors.Is(err, claim.ErrNotInvalid):
		return fail(c, fiber.StatusConflict, protocol.ErrCodeConflict, "kayıt geçersiz işaretli değil")
	case errors.Is(err, claim.ErrNoActor), errors.Is(err, claim.ErrUnverified):
		return fail(c, fiber.StatusUnauthorized, protocol.ErrCodeUnauthorized, "doğrulanmış bir hesapla giriş gerekli")
	case errors.Is(err, claim.ErrAuthRequired):
		return fail(c, fiber.StatusForbidden, protocol.ErrCodePermissionDenied,
			"üyelik sistemi kapalı: ekip kayıtları yalnızca gerçek hesapla yönetilir")
	}
	return failDomain(c, err)
}

// claimListResponse GET /api/claims yanıtıdır.
type claimListResponse struct {
	Items  []claim.Claim `json:"items"`
	Total  int           `json:"total"`
	Limit  int           `json:"limit"`
	Offset int           `json:"offset"`
}

// listClaims GET /api/claims?kind=&status=&q=&author=&limit=&offset=
// status virgülle ayrılmış liste olabilir (aktif,celiskili). Vektör yanıta girmez.
func (s *Server) listClaims(c *fiber.Ctx) error {
	actor, err := s.claimActor(c)
	if err != nil {
		return failClaim(c, err)
	}
	limit, err := s.parseLimit(c, 50)
	if err != nil {
		return err
	}
	offset, err := parseNonNegativeInt(c.Query("offset"), "offset")
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, err.Error())
	}
	f := claim.Filter{
		AuthorID: strings.TrimSpace(c.Query("author")),
		Query:    strings.TrimSpace(c.Query("q")),
	}
	if k := strings.TrimSpace(c.Query("kind")); k != "" {
		f.Kinds = []string{k}
	}
	if raw := strings.TrimSpace(c.Query("status")); raw != "" {
		for _, p := range strings.Split(raw, ",") {
			st := claim.Status(strings.TrimSpace(p))
			if !st.Valid() {
				return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest,
					"status aktif, guncellendi, celiskili ya da gecersiz olmalı")
			}
			f.Statuses = append(f.Statuses, st)
		}
	}
	items, total, err := s.deps.Claims.List(c.UserContext(), actor, f, limit, offset)
	if err != nil {
		return failClaim(c, err)
	}
	return c.JSON(claimListResponse{Items: items, Total: total, Limit: limit, Offset: offset})
}

// getClaim GET /api/claims/:id
func (s *Server) getClaim(c *fiber.Ctx) error {
	actor, err := s.claimActor(c)
	if err != nil {
		return failClaim(c, err)
	}
	cl, err := s.deps.Claims.Get(c.UserContext(), actor, strings.Clone(c.Params("id")))
	if err != nil {
		return failClaim(c, err)
	}
	return c.JSON(cl)
}

// editClaimRequest PUT /api/claims/:id gövdesidir. Tür, yazar ve durum değiştirilemez.
type editClaimRequest struct {
	Text string  `json:"text"`
	Key  *string `json:"key"` // yok/null: anahtar değişmez
	// Fields yok/null: tipli alanlar değişmez; verilirse TÜMÜ bu değerlerle değişir (şemaya göre doğrulanır).
	Fields map[string]string `json:"fields"`
	// SubjectID (Faz 8) özel türlerde (personel, pdks) kaydın ilgili kişisinin hesap kimliğidir; "" bağı
	// kaldırır (kaydı yalnızca patron görür). YALNIZCA PATRON atar.
	SubjectID *string `json:"subjectId"`
}

// editClaim PUT /api/claims/:id — metni (ve istenirse anahtarı) değiştirir; denetime yazılır.
func (s *Server) editClaim(c *fiber.Ctx) error {
	actor, err := s.claimActor(c)
	if err != nil {
		return failClaim(c, err)
	}
	var req editClaimRequest
	if err := c.BodyParser(&req); err != nil {
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, "geçersiz istek gövdesi")
	}
	cl, err := s.deps.Claims.Edit(c.UserContext(), actor, strings.Clone(c.Params("id")),
		claim.EditRequest{Text: req.Text, Key: req.Key, Fields: req.Fields, SubjectID: req.SubjectID})
	if err != nil {
		return failClaim(c, err)
	}
	return c.JSON(cl)
}

// deleteClaim DELETE /api/claims/:id — kaydı siler; silinen değer denetimde kalır.
func (s *Server) deleteClaim(c *fiber.Ctx) error {
	actor, err := s.claimActor(c)
	if err != nil {
		return failClaim(c, err)
	}
	if err := s.deps.Claims.Delete(c.UserContext(), actor, strings.Clone(c.Params("id"))); err != nil {
		return failClaim(c, err)
	}
	return c.SendStatus(fiber.StatusNoContent)
}

// claimHistoryResponse GET /api/claims/:id/history yanıtıdır.
type claimHistoryResponse struct {
	Items []claim.AuditEntry `json:"items"`
}

// claimHistory GET /api/claims/:id/history?limit=&offset= — değişiklik geçmişi, yeni önce.
// Kayıt silinmiş olsa da çalışır; yalnızca yazar ya da patron görebilir.
func (s *Server) claimHistory(c *fiber.Ctx) error {
	actor, err := s.claimActor(c)
	if err != nil {
		return failClaim(c, err)
	}
	limit, err := s.parseLimit(c, 50)
	if err != nil {
		return err
	}
	offset, err := parseNonNegativeInt(c.Query("offset"), "offset")
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, err.Error())
	}
	items, err := s.deps.Claims.History(c.UserContext(), actor, strings.Clone(c.Params("id")), limit, offset)
	if err != nil {
		return failClaim(c, err)
	}
	return c.JSON(claimHistoryResponse{Items: items})
}

// ---- Çelişki netleştirme ve geçersiz işaretleme (Faz 6) ----
//
// Dil suçlamasızdır: yanıtlar "iki farklı bilgi var, hangisi geçerli?" çerçevesindedir; kimin yanlış
// olduğu söylenmez, yazar adı yalnızca kayıtta izlenebilirlik içindir. Karar PATRONUNDUR; üye
// çelişkileri görebilir ama karar uçları 403 döner.

// claimConflictsResponse GET /api/claims/conflicts yanıtıdır.
type claimConflictsResponse struct {
	Groups []claim.ConflictGroup `json:"groups"`
	Total  int                   `json:"total"` // karar bekleyen küme sayısı
}

// listClaimConflicts GET /api/claims/conflicts — karar bekleyen çelişki kümeleri.
func (s *Server) listClaimConflicts(c *fiber.Ctx) error {
	actor, err := s.claimActor(c)
	if err != nil {
		return failClaim(c, err)
	}
	groups, err := s.deps.Claims.Conflicts(c.UserContext(), actor)
	if err != nil {
		return failClaim(c, err)
	}
	return c.JSON(claimConflictsResponse{Groups: groups, Total: len(groups)})
}

// resolveClaimRequest POST /api/claims/:id/resolve gövdesidir.
//
//	decision "keep":    :id GEÇERLİ olandır, kümedeki diğerleri geçersiz sayılır
//	decision "keepAll": aslında çelişmiyorlardı, hepsi geçerli
type resolveClaimRequest struct {
	Decision string `json:"decision"`
}

// resolveClaimConflict POST /api/claims/:id/resolve — yalnızca patron.
func (s *Server) resolveClaimConflict(c *fiber.Ctx) error {
	actor, err := s.claimActor(c)
	if err != nil {
		return failClaim(c, err)
	}
	var req resolveClaimRequest
	if err := c.BodyParser(&req); err != nil {
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, "geçersiz istek gövdesi")
	}
	res, err := s.deps.Claims.Resolve(c.UserContext(), actor, strings.Clone(c.Params("id")),
		claim.Decision(strings.TrimSpace(req.Decision)))
	if err != nil {
		return failClaim(c, err)
	}
	return c.JSON(res)
}

// invalidateClaim POST /api/claims/:id/invalidate — kaydı `gecersiz` işaretler (yalnızca patron).
func (s *Server) invalidateClaim(c *fiber.Ctx) error {
	actor, err := s.claimActor(c)
	if err != nil {
		return failClaim(c, err)
	}
	cl, err := s.deps.Claims.Invalidate(c.UserContext(), actor, strings.Clone(c.Params("id")))
	if err != nil {
		return failClaim(c, err)
	}
	return c.JSON(cl)
}

// restoreClaim POST /api/claims/:id/restore — geçersiz işaretini geri alır (yalnızca patron).
func (s *Server) restoreClaim(c *fiber.Ctx) error {
	actor, err := s.claimActor(c)
	if err != nil {
		return failClaim(c, err)
	}
	cl, err := s.deps.Claims.Restore(c.UserContext(), actor, strings.Clone(c.Params("id")))
	if err != nil {
		return failClaim(c, err)
	}
	return c.JSON(cl)
}

// ---- Hazır raporlar (Faz 9) ----

// claimReports GET /api/claims/reports?type=gecikmis_gorev|proje_durumu|proje_ozet&project=
// Sohbetteki rapor niyetiyle AYNI hesabı kullanır (claim.BuildReport): rakamlar kodda hesaplanır, metin
// (`text`) sunucuda üretilir ve OLDUĞU GİBİ gösterilir. project yalnızca proje_durumu için anlamlıdır.
// Okuyan kişinin görme sınırı uygulanır; vektör yanıta girmez.
func (s *Server) claimReports(c *fiber.Ctx) error {
	actor, err := s.claimActor(c)
	if err != nil {
		return failClaim(c, err)
	}
	req := claim.ReportRequest{
		Type:    claim.ReportType(strings.TrimSpace(c.Query("type"))),
		Project: strings.TrimSpace(c.Query("project")),
	}
	if !req.Type.Valid() {
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest,
			"type gecikmis_gorev, proje_durumu ya da proje_ozet olmalı")
	}
	if req.Type != claim.ReportProject {
		req.Project = "" // yalnızca proje_durumu proje adı alır
	}
	res, err := s.deps.Claims.Report(c.UserContext(), actor, req)
	if err != nil {
		return failClaim(c, err)
	}
	return c.JSON(res)
}
