package claim

import (
	"context"
	"errors"
	"strings"
)

// Role claim sistemindeki yetki seviyesidir (spec: `patron` ve `uye`).
//
// user.Role (admin/user) ile BİLİNÇLİ olarak ayrıdır: claim paketi hesap paketini bilmez.
// Eşleme sunucu tarafındaki bağdaştırıcıda yapılır (admin → patron, diğerleri → uye).
type Role string

const (
	// RolePatron çelişkileri netleştirir, kayıtları geçersiz işaretler, dışa aktarır.
	RolePatron Role = "patron"
	// RoleUye bilgi girer ve görür; başkasının kaydını silemez.
	RoleUye Role = "uye"
)

// Valid rol tanınan bir değer mi?
func (r Role) Valid() bool { return r == RolePatron || r == RoleUye }

var (
	// ErrAuthRequired üyelik/oturum sistemi kapalı: yazma KAPALI kalır (spec kural 3: auth bitmeden
	// yazma açılmaz). Üyelik kapalıyken tek kullanıcılı geliştirme akışı herkesi aynı kimlik sayar.
	ErrAuthRequired = errors.New("claim: gerçek oturum kimliği gerekli (üyelik kapalı)")
	// ErrUnverified userID doğrulanmış bir hesaba karşılık gelmiyor (hesap yok, devre dışı ya da
	// paylaşılan API anahtarının varsayılan kimliği). Bilgi izlenemez olacağı için yazılmaz.
	ErrUnverified = errors.New("claim: kullanıcı doğrulanmış bir hesap değil")
)

// Person hesap dizinindeki bir kişidir. Parola özeti gibi hassas alanlar TAŞINMAZ.
type Person struct {
	ID       string
	Name     string // görünen ad; boşsa kullanıcı adı
	Role     Role
	Disabled bool
}

// Directory kullanıcı kimliğini hesap kaydına çözer. Kullanıcı yoksa (nil, nil) döner;
// yalnızca depo hatası error'dur. Gerçeği user paketi tutar; claim yalnızca bu arayüzü bilir.
type Directory interface {
	Lookup(ctx context.Context, userID string) (*Person, error)
}

// Identity ajan/WS katmanından gelen userID'yi YAZMA yetkili bir Actor'a çevirir.
//
// Neden gerekli? Sohbet akışına yalnızca bir userID dizgesi ulaşır; bunun gerçek bir oturumdan mı,
// paylaşılan API anahtarından (hepsi DefaultUserID'ye düşer) mı, yoksa X-Fiko-User başlığından mı
// geldiği ayırt edilemez. Bu yüzden güven userID'nin KENDİSİNDEN değil, hesap dizininde doğrulanmış
// bir kullanıcıya karşılık gelmesinden türetilir; paylaşılan anahtarın kimliği ise açıkça dışlanır.
type Identity struct {
	dir      Directory
	realAuth bool
	blocked  map[string]struct{}
}

// NewIdentity kimlik çözücüyü kurur.
//
//   - realAuth: üyelik (oturum) sistemi AÇIK mı (cfg.Users.Enabled). false ise hiçbir kimlik yazma
//     için kabul edilmez.
//   - blocked: gerçek kişiye ait olmayan kimlikler (ör. DEFAULT_USER_ID: paylaşılan API anahtarı bu
//     kimlikle gelir). Aynı adlı gerçek bir hesap olsa bile bunlar reddedilir.
func NewIdentity(dir Directory, realAuth bool, blocked ...string) *Identity {
	b := make(map[string]struct{}, len(blocked))
	for _, id := range blocked {
		if id = strings.TrimSpace(id); id != "" {
			b[id] = struct{}{}
		}
	}
	return &Identity{dir: dir, realAuth: realAuth, blocked: b}
}

// Actor userID'yi doğrulanmış yazar bilgisine çevirir. Hata varsa YAZMA YAPILMAMALIDIR.
func (i *Identity) Actor(ctx context.Context, userID string) (Actor, error) {
	userID = strings.TrimSpace(userID)
	if userID == "" {
		return Actor{}, ErrNoActor
	}
	if i == nil || !i.realAuth || i.dir == nil {
		return Actor{}, ErrAuthRequired
	}
	if _, no := i.blocked[userID]; no {
		return Actor{}, ErrUnverified
	}
	p, err := i.dir.Lookup(ctx, userID)
	if err != nil {
		return Actor{}, err
	}
	if p == nil || p.Disabled || strings.TrimSpace(p.ID) != userID {
		return Actor{}, ErrUnverified
	}
	role := p.Role
	if !role.Valid() {
		role = RoleUye // bilinmeyen rol yetki kazandırmaz
	}
	name := strings.TrimSpace(p.Name)
	if name == "" {
		name = userID
	}
	return Actor{ID: userID, Name: name, Role: role}, nil
}
