package claim

// Çelişki yönetimi ve geçersiz işaretleme (Faz 6).
//
// KURAL (spec): çelişkili kayıtlar silinmez; ikisi de `celiskili` kalır ve HANGİSİNİN geçerli olduğunu
// PATRON belirler. Dil suçlamasızdır: sistem "iki farklı bilgi var, hangisi geçerli?" der. Üye
// çelişkileri görebilir (ekip bilgisidir) ama karar veremez.
//
// ÇELİŞKİ KÜMESİ: yeni kayıt, çeliştiği kaydı `ConflictsWith` ile gösterir. Küme, `celiskili` kayıtların
// bu bağlarla (ve listelik türlerde aynı anahtarla) bağlı bileşenidir. Bağı olmayan ESKİ çelişkili
// kayıtlar (Faz 6'dan önce yazılanlar, listelik olmayan tür) tek başına bir kümedir: karşı kaydı
// bulunamaz, bu yüzden yalnızca kendisi için karar verilebilir.
//
// KARAR (Resolve): seçilen kayıt `aktif`, kümedeki diğerleri `gecersiz` olur (keep); ya da aslında
// çelişmiyorlarsa hepsi `aktif` olur (keepAll). Her durum değişikliği denetime yazılır.
//
// TEK BAŞINA KALMA: bir çelişki kümesinden kayıt çıkınca (geçersiz işaretlendi ya da silindi) geri
// kalan kayıtlardan karşısında çelişkili kimse kalmayanlar `aktif` olur: kimseyle çelişmeyen kayıt
// "çelişkili" kalmamalı. 3'lü kümede merkez kayıt çıkarsa kalan ikisi birbirine bağlı olmadığından
// ikisi de aktif olur; sistem aralarında çelişki olup olmadığını hiç belirlememişti.

import (
	"context"
	"errors"
	"fmt"
	"sort"
	"strings"
	"time"
)

var (
	// ErrPatronOnly işlem yalnızca patron içindir (çelişki kararı, geçersiz işaretleme).
	ErrPatronOnly = errors.New("claim: bu işlemi yalnızca patron yapabilir")
	// ErrNotInConflict kayıt çelişkili durumda değil; karar verilecek bir şey yok.
	ErrNotInConflict = errors.New("claim: kayıt çelişkili değil")
	// ErrNotInvalid kayıt geçersiz işaretli değil; geri alınacak bir şey yok.
	ErrNotInvalid = errors.New("claim: kayıt geçersiz işaretli değil")
)

// Decision patronun çelişki kararıdır.
type Decision string

const (
	// DecisionKeep seçilen kayıt geçerlidir; kümedeki diğerleri geçersiz sayılır.
	DecisionKeep Decision = "keep"
	// DecisionKeepAll hepsi geçerlidir (aslında çelişmiyorlardı).
	DecisionKeepAll Decision = "keepAll"
)

// ConflictGroup birbiriyle çelişen kayıtlardır (karar bekleyen küme). Claims en eskiden yeniye.
type ConflictGroup struct {
	Kind   string  `json:"kind"`
	Key    string  `json:"key,omitempty"` // kümedeki kayıtların ortak anahtarı; farklıysa boş
	Claims []Claim `json:"claims"`
}

// StatusChange tek bir kaydın durum değişikliğidir.
type StatusChange struct {
	ID   string `json:"id"`
	From Status `json:"from"`
	To   Status `json:"to"`
}

// ResolveResult bir kararın uyguladığı değişikliklerdir.
type ResolveResult struct {
	Changes []StatusChange `json:"changes"`
}

func requirePatron(actor Actor) error {
	if strings.TrimSpace(actor.ID) == "" {
		return ErrNoActor
	}
	if actor.Role != RolePatron {
		return ErrPatronOnly
	}
	return nil
}

// conflictGroups çelişkili kayıtları bağlı bileşenlere ayırır (bkz. dosya başlığı). Çıktı kararlıdır:
// küme içinde en eskiden yeniye, kümeler tür, anahtar, ilk kayıt zamanı sırasıyla.
func conflictGroups(cs []Claim) [][]Claim {
	idx := make(map[string]int, len(cs))
	for i, c := range cs {
		idx[c.ID] = i
	}
	parent := make([]int, len(cs))
	for i := range parent {
		parent[i] = i
	}
	find := func(x int) int {
		for parent[x] != x {
			parent[x] = parent[parent[x]]
			x = parent[x]
		}
		return x
	}
	union := func(a, b int) {
		if ra, rb := find(a), find(b); ra != rb {
			parent[rb] = ra
		}
	}
	byKey := make(map[string]int)
	for i, c := range cs {
		if j, ok := idx[c.ConflictsWith]; ok && c.ConflictsWith != "" {
			union(i, j)
		}
		if IsListKind(c.Kind) && c.Key != "" {
			k := c.Kind + "\x00" + c.Key
			if j, ok := byKey[k]; ok {
				union(i, j)
			} else {
				byKey[k] = i
			}
		}
	}
	buckets := make(map[int][]Claim)
	var roots []int
	for i, c := range cs {
		r := find(i)
		if _, ok := buckets[r]; !ok {
			roots = append(roots, r)
		}
		buckets[r] = append(buckets[r], c)
	}
	out := make([][]Claim, 0, len(roots))
	for _, r := range roots {
		g := buckets[r]
		sort.SliceStable(g, func(i, j int) bool {
			if !g[i].CreatedAt.Equal(g[j].CreatedAt) {
				return g[i].CreatedAt.Before(g[j].CreatedAt)
			}
			return g[i].ID < g[j].ID
		})
		out = append(out, g)
	}
	sort.SliceStable(out, func(i, j int) bool {
		a, b := out[i][0], out[j][0]
		if a.Kind != b.Kind {
			return a.Kind < b.Kind
		}
		if a.Key != b.Key {
			return a.Key < b.Key
		}
		if !a.CreatedAt.Equal(b.CreatedAt) {
			return a.CreatedAt.Before(b.CreatedAt)
		}
		return a.ID < b.ID
	})
	return out
}

// groupContaining id'yi içeren kümeyi döner; yoksa nil.
func groupContaining(groups [][]Claim, id string) []Claim {
	for _, g := range groups {
		for _, c := range g {
			if c.ID == id {
				return g
			}
		}
	}
	return nil
}

func (s *Service) conflictClaims(ctx context.Context, kind string) ([]Claim, error) {
	f := Filter{Statuses: []Status{StatusConflict}}
	if kind != "" {
		f.Kinds = []string{kind}
	}
	return s.repo.List(ctx, f, 0, 0)
}

// conflictGroupOf kaydın bulunduğu çelişki kümesini döner; küme bulunamazsa yalnızca kaydın kendisi.
func (s *Service) conflictGroupOf(ctx context.Context, c *Claim) ([]Claim, error) {
	all, err := s.conflictClaims(ctx, c.Kind)
	if err != nil {
		return nil, err
	}
	if g := groupContaining(conflictGroups(all), c.ID); g != nil {
		return g, nil
	}
	return []Claim{*c.Snapshot()}, nil
}

// releaseOrphans bir kayıt çelişki kümesinden çıktıktan sonra geri kalanlardan karşısında çelişkili kimse
// kalmayanları aktif yapar. Hata durumunda ASIL işlem geri alınmaz (kayıt zaten silindi/işaretlendi);
// hata günlüğe yazılır ve patron kalanı elle çözebilir.
func (s *Service) releaseOrphans(ctx context.Context, actor Actor, group []Claim, removedID string, at time.Time) {
	remaining := make([]Claim, 0, len(group))
	for _, c := range group {
		if c.ID != removedID {
			remaining = append(remaining, c)
		}
	}
	for _, g := range conflictGroups(remaining) {
		if len(g) != 1 {
			continue
		}
		c := g[0]
		if err := s.setStatus(ctx, actor, &c, StatusActive, at); err != nil {
			s.log.Warn("tek başına kalan çelişkili kayıt aktif yapılamadı", "claim", c.ID, "err", err)
		}
	}
}

// Conflicts karar bekleyen çelişki kümelerini döner. Üyeler de görebilir (ekip bilgisi); karar patrondadır.
func (s *Service) Conflicts(ctx context.Context, actor Actor) ([]ConflictGroup, error) {
	if strings.TrimSpace(actor.ID) == "" {
		return nil, ErrNoActor
	}
	// Yetki süzgeci sorgudan önce uygulanır: özel türlerin çelişkileri yalnızca patrona/ilgili kişiye görünür.
	all, err := s.repo.List(ctx, actor.Scope(Filter{Statuses: []Status{StatusConflict}}), 0, 0)
	if err != nil {
		return nil, err
	}
	if err := logViews(ctx, s.audit, s.newID, s.nextTick(), actor, all); err != nil {
		return nil, fmt.Errorf("claim: erişim kaydı yazılamadı: %w", err)
	}
	groups := conflictGroups(all)
	out := make([]ConflictGroup, 0, len(groups))
	for _, g := range groups {
		key := g[0].Key
		for _, c := range g[1:] {
			if c.Key != key {
				key = ""
				break
			}
		}
		out = append(out, ConflictGroup{Kind: g[0].Kind, Key: key, Claims: g})
	}
	return out, nil
}

// Resolve patronun çelişki kararını uygular. id, karara konu kümedeki herhangi bir kayıttır: keep
// kararında GEÇERLİ olan kayıttır. Kayıt çelişkili değilse ErrNotInConflict. Her durum değişikliği
// denetime yazılır (kim, ne zaman, önceki/sonraki).
func (s *Service) Resolve(ctx context.Context, actor Actor, id string, d Decision) (*ResolveResult, error) {
	if err := requirePatron(actor); err != nil {
		return nil, err
	}
	if d != DecisionKeep && d != DecisionKeepAll {
		return nil, &ValidationError{Errors: []FieldError{{Field: "decision", Message: "keep ya da keepAll olmalı"}}}
	}
	cur, err := s.repo.Get(ctx, id)
	if err != nil {
		return nil, err
	}
	if cur.Status != StatusConflict {
		return nil, ErrNotInConflict
	}
	group, err := s.conflictGroupOf(ctx, cur)
	if err != nil {
		return nil, err
	}
	now := s.nextTick()
	res := &ResolveResult{Changes: []StatusChange{}}
	for i := range group {
		c := &group[i]
		want := StatusActive
		if d == DecisionKeep && c.ID != id {
			want = StatusInvalid
		}
		if c.Status == want {
			continue
		}
		from := c.Status
		if err := s.setStatus(ctx, actor, c, want, now); err != nil {
			return res, err
		}
		res.Changes = append(res.Changes, StatusChange{ID: c.ID, From: from, To: want})
	}
	return res, nil
}

// Invalidate kaydı `gecersiz` işaretler (yalnızca patron). Zaten geçersizse bir şey yapmaz. Kayıt bir çelişki
// kümesindeyse geride tek başına kalanlar aktif olur. Geri almak için Restore.
func (s *Service) Invalidate(ctx context.Context, actor Actor, id string) (*Claim, error) {
	if err := requirePatron(actor); err != nil {
		return nil, err
	}
	cur, err := s.repo.Get(ctx, id)
	if err != nil {
		return nil, err
	}
	if cur.Status == StatusInvalid {
		return cur.Snapshot(), nil
	}
	var group []Claim
	if cur.Status == StatusConflict {
		if group, err = s.conflictGroupOf(ctx, cur); err != nil {
			return nil, err
		}
	}
	now := s.nextTick()
	if err := s.setStatus(ctx, actor, cur, StatusInvalid, now); err != nil {
		return nil, err
	}
	if group != nil {
		s.releaseOrphans(ctx, actor, group, id, now)
	}
	out := cur.Snapshot()
	out.Status, out.UpdatedAt = StatusInvalid, now
	return out, nil
}

// Restore geçersiz işaretli kaydı aktif yapar (yalnızca patron; Invalidate'in geri alınması). Çelişki
// denetimi yeniden YAPILMAZ: geri alınan kayıt başka bir kayıtla çelişiyorsa patron bunu görüp karar verir.
func (s *Service) Restore(ctx context.Context, actor Actor, id string) (*Claim, error) {
	if err := requirePatron(actor); err != nil {
		return nil, err
	}
	cur, err := s.repo.Get(ctx, id)
	if err != nil {
		return nil, err
	}
	if cur.Status != StatusInvalid {
		return nil, ErrNotInvalid
	}
	now := s.nextTick()
	if err := s.setStatus(ctx, actor, cur, StatusActive, now); err != nil {
		return nil, err
	}
	out := cur.Snapshot()
	out.Status, out.UpdatedAt = StatusActive, now
	return out, nil
}
