package main

import (
	"context"
	"errors"
	"log/slog"
	"strings"

	"fiko-ai/config"
	"fiko-ai/internal/agent"
	"fiko-ai/internal/claim"
	"fiko-ai/internal/llm"
	"fiko-ai/internal/protocol"
	"fiko-ai/internal/user"
)

// claimEmitter yazma özetini kullanıcının açık bağlantılarına iletir (main.go: hub.BroadcastToUser).
// nil olabilir: o zaman özet yalnızca log'a gider.
type claimEmitter func(userID, conversationID string, p protocol.ClaimSummaryPayload)

// claimSummaryOf yazma sonucunu kullanıcıya gidecek özete çevirir. Gösterilecek bir şey yoksa
// (hiçbir şey eklenmedi, çelişki/eksik/hata yok) false döner: sıradan sohbet turları sessiz kalır.
// "Zaten kayıtlıydı" ve "benziyor" tek başına özet nedeni DEĞİLDİR; yalnızca bir özet varsa eklenir.
func claimSummaryOf(res *claim.WriteResult) (protocol.ClaimSummaryPayload, bool) {
	if res == nil || (res.Added == 0 && len(res.Conflicts) == 0 && res.Incomplete == 0 && res.FailedChunks == 0) {
		return protocol.ClaimSummaryPayload{}, false
	}
	return protocol.ClaimSummaryPayload{
		Text:         res.Message(),
		Added:        res.Added,
		Updated:      res.Updated,
		Conflicts:    len(res.Conflicts),
		Incomplete:   res.Incomplete,
		FailedChunks: res.FailedChunks,
	}, true
}

// claimDirectory user.Service'i claim.Directory arayüzüne uyarlar (notifyUsers ile aynı desen:
// claim paketi hesap paketini bilmez). Rol eşlemesi BURADA yapılır: admin → patron, diğerleri → üye.
type claimDirectory struct{ svc *user.Service }

// Lookup kullanıcıyı hesap deposundan çözer. Hesap yoksa (nil, nil) döner; yalnızca depo hatası error'dur.
func (d claimDirectory) Lookup(ctx context.Context, userID string) (*claim.Person, error) {
	if d.svc == nil || strings.TrimSpace(userID) == "" {
		return nil, nil
	}
	u, err := d.svc.ByID(ctx, userID)
	if err != nil {
		if errors.Is(err, user.ErrNotFound) {
			return nil, nil
		}
		return nil, err
	}
	if u == nil {
		return nil, nil
	}
	return &claim.Person{
		ID:       u.ID,
		Name:     displayNameOf(u),
		Role:     claimRoleOf(u.Role),
		Disabled: u.Disabled,
	}, nil
}

// claimRoleOf hesap rolünü claim rolüne çevirir. Bilinmeyen rol yetki kazandırmaz (üye).
func claimRoleOf(r user.Role) claim.Role {
	if r == user.RoleAdmin {
		return claim.RolePatron
	}
	return claim.RoleUye
}

// newClaimIdentity claim yazmasının kimlik çözücüsünü kurur.
//
// Yazma YALNIZCA gerçek oturum kimliğiyle açılır (spec kural 3): üyelik kapalıysa (FIKO üyelik
// sistemi yok) her istek aynı kimliktir ve X-Fiko-User başlığı güvenilmezdir; bu durumda çözücü
// ErrAuthRequired verir. DEFAULT_USER_ID paylaşılan API anahtarının kimliğidir ve kişi sayılmaz.
func newClaimIdentity(svc *user.Service, cfg config.Config) *claim.Identity {
	return claim.NewIdentity(claimDirectory{svc: svc}, cfg.Users.Enabled && svc != nil, cfg.Chat.DefaultUserID)
}

// claimParts Iddia Hafızası yazma hattının çalışma zamanı parçalarıdır.
type claimParts struct {
	svc      *claim.Service
	capturer agent.ClaimWriter
}

// setupClaims sohbetten claim yazmayı kurar. FIKO_CLAIMS_WRITE=false ise nil döner (hiçbir şey bağlanmaz).
//
// Bayrak açık olsa bile yazma YALNIZCA gerçek oturum kimliğiyle çalışır: üyelik kapalıysa
// `claim.Identity` her turu reddeder ve açılışta bu açıkça söylenir (spec kural 3).
func setupClaims(repo claim.Repository, audit claim.AuditRepository, p llm.Provider, emb llm.Embedder,
	userSvc *user.Service, cfg config.Config, log *slog.Logger, emit claimEmitter) *claimParts {
	if !cfg.Claims.Write {
		log.Info("Iddia Hafızası yazma KAPALI (FIKO_CLAIMS_WRITE=false): sohbetten ortak kayıt yazılmaz")
		return nil
	}
	if repo == nil || audit == nil || p == nil {
		log.Warn("Iddia Hafızası yazma açık ama depo/sağlayıcı yok; kapalı kalıyor")
		return nil
	}
	id := newClaimIdentity(userSvc, cfg)
	if !cfg.Users.Enabled || userSvc == nil {
		log.Warn("Iddia Hafızası yazma açık ama üyelik sistemi KAPALI (FIKO_USERS_ENABLED=false): " +
			"gerçek oturum kimliği olmadan hiçbir kayıt yazılmayacak")
	}
	if cfg.Ollama.EmbedModel == "" {
		emb = nil
		log.Warn("Iddia Hafızası embedding olmadan çalışıyor (OLLAMA_EMBED_MODEL boş): benzerlik denetimi yapılmaz")
	}
	svc := claim.NewService(repo, audit, p, emb, claim.Config{
		Model: cfg.Ollama.Model, EmbedModel: cfg.Ollama.EmbedModel,
	}, log)
	log.Info("Iddia Hafızası yazma ETKİN", "üyelik", cfg.Users.Enabled, "embedding", cfg.Ollama.EmbedModel != "")
	return &claimParts{svc: svc, capturer: claimCapturer{svc: svc, id: id, log: log, emit: emit}}
}

// setupClaimManager ekip kayıtlarının yönetim uçlarının (/api/claims: görüntüle, düzenle, sil, geçmiş)
// servisini ve kimlik çözücüsünü kurar (Faz 5). Silme AKTİFTİR (spec kural 2) ve yazma bayrağına
// bağlı DEĞİLDİR; ama yetki yalnızca doğrulanmış hesap kimliğiyle verilir: üyelik kapalıyken uçlar
// reddeder (kimlik çözücü ErrAuthRequired verir) ve bu açılışta söylenir.
//
// Yazma servisi (FIKO_CLAIMS_WRITE=true) zaten varsa AYNI örnek paylaşılır; yoksa yalnızca yönetime
// yarayan, arka plan işi olmayan ayrı bir servis kurulur (çıkarım/çakışma kararı hiç çağrılmaz).
func setupClaimManager(shared *claim.Service, repo claim.Repository, audit claim.AuditRepository,
	p llm.Provider, emb llm.Embedder, userSvc *user.Service, cfg config.Config, log *slog.Logger) (*claim.Service, *claim.Identity) {
	if repo == nil || audit == nil {
		log.Warn("Iddia Hafızası yönetimi kapalı: depo yok")
		return nil, nil
	}
	svc := shared
	if svc == nil {
		if cfg.Ollama.EmbedModel == "" {
			emb = nil
		}
		svc = claim.NewService(repo, audit, p, emb, claim.Config{
			Model: cfg.Ollama.Model, EmbedModel: cfg.Ollama.EmbedModel,
		}, log)
	}
	if userSvc != nil {
		// Kaydın ilgili kişisi (subjectId) yalnızca gerçek bir hesaba atanabilir (Faz 8).
		svc.SetDirectory(claimDirectory{svc: userSvc})
	}
	if !cfg.Users.Enabled || userSvc == nil {
		log.Warn("Iddia Hafızası yönetimi açık ama üyelik sistemi KAPALI (FIKO_USERS_ENABLED=false): " +
			"/api/claims uçları gerçek oturum kimliği olmadan reddedecek")
	} else {
		log.Info("Iddia Hafızası yönetimi ETKİN: /api/claims (görüntüle, düzenle, sil, geçmiş); düzenle/sil yalnızca yazar ya da patron")
	}
	return svc, newClaimIdentity(userSvc, cfg)
}

// closeClaims bekleyen arka plan yazmalarını bitirir (ya da süre dolunca bırakır).
func closeClaims(p *claimParts) {
	if p == nil || p.svc == nil {
		return
	}
	p.svc.Close()
}

// claimCapturer agent.ClaimWriter'ı claim.Service'e bağlar: önce kimliği doğrular, sonra arka plana alır.
type claimCapturer struct {
	svc  *claim.Service
	id   *claim.Identity
	log  *slog.Logger
	emit claimEmitter // nil olabilir: özet yalnızca log'a gider
}

// Capture hata dönmez: kimlik doğrulanamazsa sessizce atlar (sohbet bozulmaz). Mesaj içeriği
// (kişisel veri olabilir) log'a YAZILMAZ; yalnızca sayılar.
func (c claimCapturer) Capture(ctx context.Context, req agent.ClaimRequest) {
	if c.svc == nil || c.id == nil {
		return
	}
	actor, err := c.id.Actor(ctx, req.UserID)
	if err != nil {
		c.log.Debug("claim yazılmadı: kimlik doğrulanamadı", "neden", err.Error(), "conversation", req.ConversationID)
		return
	}
	conv := req.ConversationID
	if err := c.svc.WriteAsync(actor, req.Text, func(res *claim.WriteResult, werr error) {
		if werr != nil {
			c.log.Warn("claim yazılamadı", "err", werr, "conversation", conv)
			return
		}
		if sum, ok := claimSummaryOf(res); ok {
			c.log.Info("claim yazıldı", "özet", sum.Text, "conversation", conv)
			if c.emit != nil {
				c.emit(req.UserID, conv, sum)
			}
		}
	}); err != nil {
		c.log.Warn("claim kuyruğa alınamadı", "err", err, "conversation", conv)
	}
}
