package tool

import (
	"errors"
	"fmt"
	"sort"
	"strings"
	"sync"
)

// ErrPermissionDenied izin eksikliğinden dönen hatanın köküdür (errors.Is ile eşleşir).
var ErrPermissionDenied = errors.New("tool: yetki yok")

// Permission spec'in izin kümesidir: READ, WRITE, EXECUTE, DELETE, SYSTEM.
// Bit maskesi olarak tutulur: birden fazla izin tek alanda taşınır ve karşılaştırması ucuzdur.
type Permission uint8

const (
	PermRead    Permission = 1 << 0
	PermWrite   Permission = 1 << 1
	PermExecute Permission = 1 << 2
	PermDelete  Permission = 1 << 3
	PermSystem  Permission = 1 << 4

	// PermNone hiçbir izin.
	PermNone Permission = 0
	// PermAll tüm izinler.
	PermAll = PermRead | PermWrite | PermExecute | PermDelete | PermSystem
)

// permissionNames sıralı izin adlarıdır (String/Names çıktısı kararlı olsun).
var permissionNames = []struct {
	p Permission
	n string
}{
	{PermRead, "read"},
	{PermWrite, "write"},
	{PermExecute, "execute"},
	{PermDelete, "delete"},
	{PermSystem, "system"},
}

// ParsePermission izin adını çevirir ("WRITE", " write " → PermWrite).
func ParsePermission(s string) (Permission, error) {
	needle := strings.ToLower(strings.TrimSpace(s))
	for _, e := range permissionNames {
		if e.n == needle {
			return e.p, nil
		}
	}
	return PermNone, fmt.Errorf("tool: bilinmeyen izin %q (read, write, execute, delete, system)", s)
}

// PermissionsOf izin adlarını tek maskeye çevirir; bilinmeyen ad hata verir.
// Boş girdi PermNone döner: "izin yok" ile "izin var" karıştırılmamalı.
func PermissionsOf(names []string) (Permission, error) {
	var out Permission
	for _, n := range names {
		p, err := ParsePermission(n)
		if err != nil {
			return PermNone, err
		}
		out |= p
	}
	return out, nil
}

// Has istenen izinlerin TAMAMI veriliyor mu?
func (p Permission) Has(required Permission) bool { return p&required == required }

// Names(mask) izinleri kanonik sırada ad olarak döndürür.
func (p Permission) Names() []string {
	if p == PermNone {
		return nil
	}
	out := make([]string, 0, len(permissionNames))
	for _, e := range permissionNames {
		if p&e.p != 0 {
			out = append(out, e.n)
		}
	}
	return out
}

// Missing required içinde p'de OLMAYAN izinleri döndürür (hata iletisinde kullanılır).
func (p Permission) Missing(required Permission) Permission { return required &^ p }

// String maskeyi okunur biçimde yazar.
func (p Permission) String() string {
	if p == PermNone {
		return "none"
	}
	return strings.Join(p.Names(), ",")
}

// Policy bir kullanıcının çalıştırabileceği tool kümesini tanımlar.
// Grants yalnızca verilen izinleri içerir; risk kapıları ayrı alanlardır.
type Policy struct {
	Grants        Permission `json:"-"`
	AllowHighRisk bool       `json:"allowHighRisk"` // HIGH risk tool'lara izin
	AllowCritical bool       `json:"allowCritical"` // CRITICAL risk tool'lara izin
}

// PolicyView politikanın API/ayar ekranı için okunur halidir (izinler ad olarak döner).
type PolicyView struct {
	Grants        []string `json:"grants"` // boşsa null değil: []
	AllowHighRisk bool     `json:"allowHighRisk"`
	AllowCritical bool     `json:"allowCritical"`
}

// View politikanın okunur halini döndürür.
func (p Policy) View() PolicyView {
	grants := p.Grants.Names()
	if grants == nil {
		grants = []string{}
	}
	return PolicyView{Grants: grants, AllowHighRisk: p.AllowHighRisk, AllowCritical: p.AllowCritical}
}

// DefaultPolicy güvenli başlangıçtır: read + write + execute. delete ve system YOKTUR.
//
// Spec: "AI kendisine sınırsız yetki vermemeli" — bu yüzden geri alınamaz (delete) ve
// sistem (system) işlemler varsayılan olarak KAPALI kalır.
//
// CANLI HATA (04.10.2026): varsayılan yalnızca "execute" idi ve bu yüzden ŞİRKET
// tool'larının çoğu (biz.add/find/summary/update/project) modele HİÇ SUNULMUYORDU.
// Kullanıcı "proje silme aracı yok" diyordu; araç kodda vardı ama görünmüyordu.
//
// Okuma/yazma izni uygulamanın KENDİ verisine erişimi açar: şirket kayıtları (biz.*)
// ve kalıcı bellek. Dosya sistemi tool'ları zaten TOOL_FS_ROOT sandbox'ına yazıyor,
// süreç çalıştırma (sys.exec) ise ayrıca TOOL_EXEC_ENABLED + tam yol gerektiriyor.
func DefaultPolicy() Policy { return Policy{Grants: PermRead | PermWrite | PermExecute} }

// PermissionError yetki reddidir; hangi iznin (veya hangi riskin) eksik olduğunu taşır.
type PermissionError struct {
	Tool    string
	Missing Permission // eksik izinler
	Risk    Risk       // risk kapısı reddiyse dolu
}

func (e *PermissionError) Error() string {
	if e.Risk != "" {
		return fmt.Sprintf("tool %q çalıştırılamaz: %s riskli işlemler için izin verilmedi",
			e.Tool, strings.ToUpper(string(e.Risk)))
	}
	return fmt.Sprintf("tool %q çalıştırılamaz: eksik izin %s", e.Tool, e.Missing)
}

// Is hata köküyle eşleşir: errors.Is(err, ErrPermissionDenied) her zaman doğru olur.
func (e *PermissionError) Is(target error) bool { return target == ErrPermissionDenied }

// Required bir tool'un çalıştırılması için gereken izin maskesidir.
// Tool kendi izinlerini bildirir; "execute" HİÇBİR ZAMAN bildirilmezse bile gerekir —
// aksi hâlde "izin yok" tool'ları risksiz görünür.
func Required(t *Tool) Permission {
	required, err := PermissionsOf(t.Permissions)
	if err != nil {
		required = PermNone
	}
	return required | PermExecute
}

// Authorize policy'nin bu tool'u çalıştırmaya izin verip vermediğini denetler.
// Sıra önemlidir: önce izinler, sonra risk kapıları (kullanıcıya en alakalı eksik söylenir).
func (p Policy) Authorize(t *Tool) error {
	if t == nil {
		return ErrNotFound
	}
	if missing := p.Grants.Missing(Required(t)); missing != PermNone {
		return &PermissionError{Tool: t.Name, Missing: missing}
	}
	switch {
	case t.Risk == RiskCritical && !p.AllowCritical:
		return &PermissionError{Tool: t.Name, Risk: RiskCritical}
	case t.Risk == RiskHigh && !p.AllowHighRisk:
		return &PermissionError{Tool: t.Name, Risk: RiskHigh}
	}
	return nil
}

// Allows yalnızca yetki var mı diye bakar (hata üretmez); arayüzdeki "izin ver" düğmesi için.
func (p Policy) Allows(t *Tool) bool { return p.Authorize(t) == nil }

// GrantStore kullanıcı başına izin politikası tutar.
// Faz 4'te bellek içidir (kimlik doğrulama yok, süreç ömrü = sunucu ömrü);
// kalıcı politika ayarları ayarlar/veritabanı katmanına taşınacak.
type GrantStore struct {
	mu     sync.RWMutex
	def    Policy
	byUser map[string]Policy
}

// NewGrantStore varsayılan politikayla depo kurar.
func NewGrantStore(def Policy) *GrantStore {
	return &GrantStore{def: def, byUser: make(map[string]Policy)}
}

// Default varsayılan politikayı döndürür.
func (s *GrantStore) Default() Policy {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return s.def
}

// PolicyFor kullanıcının politikasını döndürür; tanımlı değilse varsayılan döner.
// Kullanıcı boşsa da varsayılan döner: kimlik çözülemeyen istekler kısıtlı kalmalı.
func (s *GrantStore) PolicyFor(userID string) Policy {
	s.mu.RLock()
	defer s.mu.RUnlock()
	if p, ok := s.byUser[strings.TrimSpace(userID)]; ok {
		return p
	}
	return s.def
}

// SetPolicy kullanıcı politikasını atar; boş kullanıcı VARSAYILANI değiştirir.
func (s *GrantStore) SetPolicy(userID string, p Policy) {
	s.mu.Lock()
	defer s.mu.Unlock()
	userID = strings.TrimSpace(userID)
	if userID == "" {
		s.def = p
		return
	}
	s.byUser[userID] = p
}

// Users politikası değiştirilmiş kullanıcı kimliklerini sıralı döndürür (arayüz listesi).
func (s *GrantStore) Users() []string {
	s.mu.RLock()
	defer s.mu.RUnlock()
	out := make([]string, 0, len(s.byUser))
	for u := range s.byUser {
		out = append(out, u)
	}
	sort.Strings(out)
	return out
}
