// Package persona, kullanıcıya özel YAPAY ZEKA DAVRANIŞI tanımlar.
//
// Konsept: yönetici, tekrar kullanılabilir bir "profil" (ör. Proje Yöneticisi)
// yazar ve onu bir veya birden çok kullanıcıya atar. Atanmış kullanıcıyla
// konuşulurken profil kuralları sistem istemine eklenir; yapay zekâ o kurallara
// uyar (ör. "proje eklerken eksiksiz bilgi al").
//
// Neden ayrı paket? (1) Profil KULLANICIYA özeldir; sistem istemi parçaları
// geneldir. (2) Depo katmanı (Mongo) bu paketi bilmez; bağımlılık yönü tek
// yönlü kalır: api/persona bu paketi kullanır.
package persona

import (
	"context"
	"errors"
	"strings"
	"time"

	"fiko-ai/pkg/id"
)

// Sınırlar. Neden var? Profil metni DOĞRUDAN modele girer; sınırsız metin
// istemi şişirir (token), hem de yöneticinin kazara devasa metin yapıştırmasını
// engeller. Değerler kasıtlı olarak cömert: gerçek bir rol profili rahat sığar.
const (
	MaxName         = 80
	MaxRole         = 200
	MaxInstructions = 4000
	MaxNotes        = 1000
	// MaxAssignments tek profile atanabilecek kullanıcı sayısı üst sınırı.
	MaxAssignments = 500
)

// Profile, tekrar kullanılabilir bir davranış şablonudur.
type Profile struct {
	ID string `json:"id" bson:"_id"`
	// Name yöneticinin verdiği ad ("Proje Yöneticisi"); listelerde görünür.
	Name string `json:"name" bson:"name"`
	// Role tek cümlelik rol tanımı ("Bu kişi proje yöneticisidir.").
	Role string `json:"role" bson:"role"`
	// Instructions asıl davranış kurallarıdır; modele olduğu gibi gider.
	Instructions string `json:"instructions" bson:"instructions"`
	// Notes yalnız bu profille çalışan kullanıcılar için ince ayar notları
	// (profil metnini değiştirmeden "sadece Ahmet'e uygula" denebilir).
	Notes string `json:"notes" bson:"notes"`
	// AssignedUserIDs profille eşleştirilen kullanıcı kimlikleridir.
	AssignedUserIDs []string  `json:"assignedUserIds" bson:"assignedUserIds"`
	CreatedAt       time.Time `json:"createdAt" bson:"createdAt"`
	UpdatedAt       time.Time `json:"updatedAt" bson:"updatedAt"`
}

// HasUser, profilin verilen kullanıcıya atanıp atanmadığını söyler.
func (p Profile) HasUser(userID string) bool {
	if userID == "" {
		return false
	}
	for _, u := range p.AssignedUserIDs {
		if u == userID {
			return true
		}
	}
	return false
}

// Clone, profilin bağımsız kopyasını döner.
//
// Neden? Depo çağıranlara (servis, istemci) doğrudan bellekteki dilimi vermek
// YARIŞ DURUMU yaratır: çağıran `AssignedUserIDs`'i değiştirir veya `append` ile
// arka diziyi ezer. Kopyalama bu riski kökten keser (notify.Message.Clone
// deseni). Atama listesi de kopyalanır — bu dizi en sık değişen alan.
func (p Profile) Clone() Profile {
	out := p
	if p.AssignedUserIDs != nil {
		out.AssignedUserIDs = append([]string(nil), p.AssignedUserIDs...)
	}
	return out
}

// ErrNotFound, kayıt bulunamadığında dönen hata (REST 404'e çevrilir).
var ErrNotFound = errors.New("persona: profil bulunamadı")

// ErrForbidden, yönetici olmayan işlem denediğinde döner (REST 403).
var ErrForbidden = errors.New("persona: yetkisiz")

// FieldError, tek alan hatasıdır (arayüz alan altında gösterir).
type FieldError struct {
	Field   string `json:"field"`
	Message string `json:"message"`
}

// ValidationError, girdi doğrulaması başarısız olduğunda döner (REST 400).
type ValidationError struct{ Errors []FieldError }

func (e *ValidationError) Error() string {
	if len(e.Errors) == 0 {
		return "persona: geçersiz"
	}
	return "persona: geçersiz (" + e.Errors[0].Field + ": " + e.Errors[0].Message + ")"
}

// Invalid, alan hataları taşıyan doğrulama hatası üretir.
func Invalid(errs ...FieldError) error { return &ValidationError{Errors: errs} }

// IsValidation, hatanın doğrulama hatası olup olmadığını söyler.
func IsValidation(err error) bool {
	var ve *ValidationError
	return errors.As(err, &ve)
}

// FieldErrors, doğrulama hatasının alanlarını döner (doğrulama hatası değilse boş).
func FieldErrors(err error) []FieldError {
	var ve *ValidationError
	if !errors.As(err, &ve) {
		return nil
	}
	return ve.Errors
}

// NewInput, yeni profil oluşturma girdisidir.
type NewInput struct {
	Name            string
	Role            string
	Instructions    string
	Notes           string
	AssignedUserIDs []string
	Now             time.Time
}

// New, girdiyi doğrulayıp profili üretir.
//
// DOĞRULAMA İLKESİ: kırpmak (trim) yeterli değil, UZUNLUK da sınırlanır —
// çünkü bu metin modele gider. Boş talimat işe yaramayan profildir; yönetici
// "bu davranış olmasın" demek istiyorsa profili siler.
func New(in NewInput) (Profile, error) {
	p := Profile{
		ID:              id.New("per"),
		Name:            strings.TrimSpace(in.Name),
		Role:            strings.TrimSpace(in.Role),
		Instructions:    strings.TrimSpace(in.Instructions),
		Notes:           strings.TrimSpace(in.Notes),
		AssignedUserIDs: normalizeIDs(in.AssignedUserIDs),
		CreatedAt:       in.Now,
		UpdatedAt:       in.Now,
	}
	if err := p.Validate(); err != nil {
		return Profile{}, err
	}
	return p, nil
}

// UpdateInput, mevcut profili güncelleme girdisidir.
type UpdateInput struct {
	Name            string
	Role            string
	Instructions    string
	Notes           string
	AssignedUserIDs []string
	Now             time.Time
}

// Apply, profili günceller (kimlik ve oluşturma zamanı DEĞİŞMEZ).
func (p *Profile) Apply(in UpdateInput) error {
	p.Name = strings.TrimSpace(in.Name)
	p.Role = strings.TrimSpace(in.Role)
	p.Instructions = strings.TrimSpace(in.Instructions)
	p.Notes = strings.TrimSpace(in.Notes)
	p.AssignedUserIDs = normalizeIDs(in.AssignedUserIDs)
	p.UpdatedAt = in.Now
	return p.Validate()
}

// Validate, alan sınırlarını denetler. Tüm hataları birlikte döner ki arayüz
// tek turda hepsini görebilsin.
func (p Profile) Validate() error {
	var errs []FieldError
	if p.Name == "" {
		errs = append(errs, FieldError{"name", "profil adı zorunludur"})
	} else if len([]rune(p.Name)) > MaxName {
		errs = append(errs, FieldError{"name", "profil adı çok uzun"})
	}
	if len([]rune(p.Role)) > MaxRole {
		errs = append(errs, FieldError{"role", "rol tanımı çok uzun"})
	}
	if p.Instructions == "" {
		errs = append(errs, FieldError{"instructions", "davranış talimatı zorunludur"})
	} else if len([]rune(p.Instructions)) > MaxInstructions {
		errs = append(errs, FieldError{"instructions", "davranış talimatı çok uzun"})
	}
	if len([]rune(p.Notes)) > MaxNotes {
		errs = append(errs, FieldError{"notes", "not çok uzun"})
	}
	if len(p.AssignedUserIDs) > MaxAssignments {
		errs = append(errs, FieldError{"assignedUserIds", "çok fazla kullanıcı atanmış"})
	}
	if len(errs) > 0 {
		return Invalid(errs...)
	}
	return nil
}

// normalizeIDs kimlik listesini temizler: boş/tekrar edenler düşer, sıra korunur.
//
// Neden? Yönetici aynı kullanıcıyı iki kez seçebilir; isteme giren metin
// değişmez ama arayüz "2 kullanıcı" derken gerçekte 1 olurdu (kafa karışır).
func normalizeIDs(in []string) []string {
	seen := make(map[string]bool, len(in))
	out := make([]string, 0, len(in))
	for _, raw := range in {
		v := strings.TrimSpace(raw)
		if v == "" || seen[v] {
			continue
		}
		seen[v] = true
		out = append(out, v)
	}
	return out
}

// Repository, profillerin saklandığı yeri tanımlar.
//
// Neden arayüz? Hafıza (memory) ve bildirim (notify) deseni: testte bellek içi
// uygulama, üretimde Mongo uygulaması değiştirilebilir.
type Repository interface {
	List(ctx context.Context) ([]Profile, error)
	// GetByUser, kullanıcıya atanmış profilleri döner. Kullanıcı birden çok
	// profil alabilir (örn. rol + kişisel ek davranış).
	GetByUser(ctx context.Context, userID string) ([]Profile, error)
	Get(ctx context.Context, id string) (Profile, error)
	Create(ctx context.Context, p Profile) (Profile, error)
	Update(ctx context.Context, p Profile) (Profile, error)
	Delete(ctx context.Context, id string) error
}
