package main

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

	"fiko-ai/internal/api/ws"
	"fiko-ai/internal/notify"
	"fiko-ai/internal/tool"
	"fiko-ai/internal/user"
	"fiko-ai/pkg/text"
)

// notify.go: notify paketini kullanıcı servisine bağlar.
//
// Neden burada? notify, `user` paketini BİLMEMELİ (bağımlılık yönü); main.go iki
// tarafı birleştirir. Bu, memory.NewRecaller ve ws.SetNotify desenlerinin aynısı.

// notifyUsers, user.Service'i notify.UserResolver arayüzüne uyarlar.
type notifyUsers struct{ svc *user.Service }

// DisplayName, kimliğin görünen adını çözer. Kullanıcı yoksa HATA döner; servis
// bunu yakalayıp adı boş bırakır ve bildirim yine gönderilir (yanlış ad göstermek
// tense adsız göstermek doğrudur).
func (a notifyUsers) DisplayName(ctx context.Context, userID string) (string, error) {
	if a.svc == nil {
		return "", notify.ErrNotFound
	}
	users, err := a.svc.List(ctx, 1000)
	if err != nil {
		return "", err
	}
	for _, u := range users {
		if u != nil && u.ID == userID {
			return displayNameOf(u), nil
		}
	}
	return "", notify.ErrNotFound
}

// displayNameOf, görünen ad yoksa kullanıcı adına düşer (boş gönderen adı yalnızca
// kullanıcının adı da boşsa olur).
func displayNameOf(u *user.User) string {
	if u.DisplayName != "" {
		return u.DisplayName
	}
	return u.Username
}

// notifyRecipients, user.Service'i notify.Recipients arayüzüne uyarlar.
//
// GÜVENLİK: arama sonucu modele SADECE kimlik + görünen ad döner (e-posta/rol/
// devre dışı bilgisi sızmaz). Devre dışı kullanıcılar Resolve'ta ELENİR: kimseye
// mesaj gönderilemez.
type notifyRecipients struct{ svc *user.Service }

// activeUsers, devre dışı olanları eler.
func (a notifyRecipients) activeUsers(ctx context.Context, in []*user.User) []notify.UserRef {
	out := make([]notify.UserRef, 0, len(in))
	for _, u := range in {
		if u == nil || u.Disabled {
			continue
		}
		// Username de taşınır: arama görünen adı boş olan kullanıcıyı bulabilsin
		// (canlı hata 05.10.2026 — model "admin" aramasında 0 sonuç alıp isim uydurdu).
		out = append(out, notify.UserRef{
			ID: u.ID, DisplayName: displayNameOf(u), Username: u.Username,
		})
	}
	return out
}

func (a notifyRecipients) Search(ctx context.Context, q string, limit int) ([]notify.UserRef, error) {
	if a.svc == nil {
		return nil, notify.ErrNotFound
	}
	if limit <= 0 {
		limit = 5
	}
	users, err := a.svc.List(ctx, 1000)
	if err != nil {
		return nil, err
	}
	needle := text.FoldTR(q)
	// En az 2 karakter: tek harf araması neredeyse tüm listeyi döndürür ve modele
	// kullanılamaz bir seçenek yığını sunar.
	if len([]rune(needle)) < 2 {
		return nil, nil
	}
	out := make([]notify.UserRef, 0, limit)
	for _, u := range a.activeUsers(ctx, users) {
		// ÜÇ alanda ara: görünen ad, kullanıcı adı VE kimlik — hepsi TÜRKÇE KATLANMIŞ.
		//
		// CANLI HATA (05.10.2026): "notify.users → count:0" → model kullanıcı adını
		// UYDURDU. İki ayrı sebep vardı:
		//  1. Arama yalnız DisplayName ve ID'ye bakıyordu; görünen adı boş olan
		//     kullanıcılarda "admin" yazınca Username="admin" olmasına rağmen
		//     eşleşme bulunamıyordu.
		//  2. Türkçe katlama yoktu: "Ahmet Yılmaz"a "yilmaz" yazılınca "ı" ile "i"
		//     farkı tutmuyordu. Katlama artık pkg/text'ten gelir (agent ile aynı).
		name := text.FoldTR(u.DisplayName)
		user := text.FoldTR(u.Username)
		id := text.FoldTR(u.ID)
		if strings.Contains(name, needle) || strings.Contains(user, needle) || strings.Contains(id, needle) {
			out = append(out, u)
			if len(out) >= limit {
				break
			}
		}
	}
	return out, nil
}

// Resolve, verilen kimliklerin ETKİN olanlarını döndürür. Kimlik yoksa veya devre
// dışıysa SESSİZCE elenir (modele "bu kullanıcı yok" demek yerine listeden
// düşmesi daha doğru: gönderim zaten katılım denetiminden geçmezse hata döner).
func (a notifyRecipients) Resolve(ctx context.Context, userIDs []string) ([]notify.UserRef, error) {
	if a.svc == nil {
		return nil, notify.ErrNotFound
	}
	users, err := a.svc.List(ctx, 1000)
	if err != nil {
		return nil, err
	}
	byID := make(map[string]*user.User, len(users))
	for _, u := range users {
		if u != nil {
			byID[u.ID] = u
		}
	}
	// SIRAYA GÖRE DÖNDÜR: model girdi sırasını korumak ister.
	out := make([]notify.UserRef, 0, len(userIDs))
	for _, id := range userIDs {
		if u, ok := byID[id]; ok && !u.Disabled {
			out = append(out, notify.UserRef{
				ID: u.ID, DisplayName: displayNameOf(u), Username: u.Username,
			})
		}
	}
	return out, nil
}

// registerNotifyTools, notify tool'larını registry'ye kaydeder.
//
// KOŞUL: servis ve depo bağlı olmalıdır; değilse tool'lar KAYDEDİLMEZ (modele
// "kullanılamıyor" görünen ama çalışmayan araç vermekten iyidir).
func registerNotifyTools(ctx context.Context, svc *notify.Service, reg *tool.Registry, log *slog.Logger) {
	if svc == nil || reg == nil {
		return
	}
	set := notify.NewToolSet(svc)
	defs := set.Definitions()
	if len(defs) == 0 {
		return
	}
	for _, d := range defs {
		if _, _, err := reg.EnsureBuiltin(ctx, d.Tool, d.Handler, true); err != nil {
			log.Warn("bildirim tool kaydı başarısız", "tool", d.Tool.Name, "err", err)
			continue
		}
		// Kod alanları değişmişse kayıtları tazele (bellek tool'larındaki ders):
		// veritabanındaki eski izin modele sunulmayı engelleyebilir.
		if err := reg.SyncBuiltinFields(ctx, d.Tool); err != nil {
			log.Warn("bildirim tool alanları tazelenemedi", "tool", d.Tool.Name, "err", err)
		}
	}
	log.Info("bildirim tool'ları ETKİN: notify.users / notify.send / notify.ask / notify.answers")
}

// notifyRepoName, log satırında depo türünü okunur göstermek için kullanılır
// ("memory" | "mongo"). Operasyonel bilgi: sunucu yeniden başlatıldığında bildirim
// KAYBOLUYORSA bu satırda görülür.
func notifyRepoName(r notify.Repository) string {
	if r == nil {
		return "yok"
	}
	if _, ok := r.(*notify.MemoryRepository); ok {
		return "bellek (geçici!)"
	}
	return "mongo (kalıcı)"
}

// notifyBridgeFor, notify.Service + kullanıcı servisini ws.NotifyBridge'e çevirir.
//
// KÖPRÜ NEDEN BURADA? ws.notifyBridge iç alandır (ws paketi notify'yi bilmemeli),
// main.go ise iki tarafı bağlayan tek yerdir (memoryQuestions deseniyle aynı).
//
// users parametresi DisplayName çözümü için gerekir: yanıt kaydı depoda ADSIZ
// yazılır (bkz. mongostore.Answer), arayüz "Veli sana yanıt verdi" diyebilmek
// için ad burada çözülür.
func notifyBridgeFor(svc *notify.Service, users *user.Service) ws.NotifyBridge {
	resolver := notifyUsers{svc: users}
	return ws.NotifyBridge{
		List: func(ctx context.Context, userID, box string) ([]notify.Message, error) {
			items, _, err := svc.List(ctx, userID, notify.Filter{Box: box, Limit: 100})
			return items, err
		},
		MarkRead: svc.MarkRead,
		Answer: func(ctx context.Context, userID, questionID, answer string) error {
			_, _, err := svc.Answer(ctx, userID, questionID, answer)
			return err
		},
		Counts: func(ctx context.Context, userID string) (int, int) {
			return svc.Unread(ctx, userID), len(svc.PendingQuestions(userID))
		},
		DisplayName: func(ctx context.Context, userID string) string {
			name, err := resolver.DisplayName(ctx, userID)
			if err != nil {
				return "" // bilinmeyen gönderen: yanlış ad yerine boş
			}
			return name
		},
	}
}

// _ arayüz doğrulaması: uyarlamalar imzaları doğru taşıyor.
var (
	_ notify.UserResolver = notifyUsers{}
	_ notify.Recipients   = notifyRecipients{}
)
