package repository

import (
	"context"
	"sort"
	"sync"

	"apps/backend/internal/module/studio/domain"
)

type MemoryStore struct {
	mu       sync.Mutex
	personas map[string]*domain.Persona
	active   map[int64]string
	plays    map[string]*domain.Play
	outbox   map[string]*domain.OutboxBundle
	ownPosts map[string]*domain.OwnPost
	mentions map[string]*domain.Mention
	syncMeta map[int64]*domain.SyncMeta
}

func NewMemory() *MemoryStore {
	return &MemoryStore{
		personas: map[string]*domain.Persona{},
		active:   map[int64]string{},
		plays:    map[string]*domain.Play{},
		outbox:   map[string]*domain.OutboxBundle{},
		ownPosts: map[string]*domain.OwnPost{},
		mentions: map[string]*domain.Mention{},
		syncMeta: map[int64]*domain.SyncMeta{},
	}
}

func (s *MemoryStore) SavePersona(_ context.Context, p *domain.Persona) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	cp := *p
	s.personas[p.ID] = &cp
	return nil
}

func (s *MemoryStore) GetPersona(_ context.Context, id string) (*domain.Persona, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	p, ok := s.personas[id]
	if !ok {
		return nil, domain.ErrNotFound
	}
	cp := *p
	return &cp, nil
}

func (s *MemoryStore) ListPersonas(_ context.Context, ownerUID int64) ([]*domain.Persona, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	var out []*domain.Persona
	for _, p := range s.personas {
		if p.OwnerUID == ownerUID {
			cp := *p
			out = append(out, &cp)
		}
	}
	sort.Slice(out, func(i, j int) bool { return out[i].UpdatedAt > out[j].UpdatedAt })
	return out, nil
}

func (s *MemoryStore) DeletePersona(_ context.Context, id string) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if _, ok := s.personas[id]; !ok {
		return domain.ErrNotFound
	}
	delete(s.personas, id)
	return nil
}

func (s *MemoryStore) GetActivePersonaID(_ context.Context, ownerUID int64) (string, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.active[ownerUID], nil
}

func (s *MemoryStore) SetActivePersonaID(_ context.Context, ownerUID int64, personaID string) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.active[ownerUID] = personaID
	return nil
}

func (s *MemoryStore) SavePlay(_ context.Context, p *domain.Play) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	cp := *p
	if p.Steps != nil {
		cp.Steps = append([]domain.PlayStep(nil), p.Steps...)
	}
	if p.CastAccountIDs != nil {
		cp.CastAccountIDs = append([]string(nil), p.CastAccountIDs...)
	}
	if p.TargetExternal != nil {
		te := *p.TargetExternal
		cp.TargetExternal = &te
	}
	s.plays[p.ID] = &cp
	return nil
}

func (s *MemoryStore) GetPlay(_ context.Context, id string) (*domain.Play, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	p, ok := s.plays[id]
	if !ok {
		return nil, domain.ErrNotFound
	}
	return copyPlay(p), nil
}

func (s *MemoryStore) ListPlays(_ context.Context, ownerUID int64) ([]*domain.Play, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	var out []*domain.Play
	for _, p := range s.plays {
		if p.OwnerUID == ownerUID {
			out = append(out, copyPlay(p))
		}
	}
	sort.Slice(out, func(i, j int) bool { return out[i].UpdatedAt > out[j].UpdatedAt })
	return out, nil
}

func (s *MemoryStore) DeletePlay(_ context.Context, id string) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if _, ok := s.plays[id]; !ok {
		return domain.ErrNotFound
	}
	delete(s.plays, id)
	return nil
}

func copyPlay(p *domain.Play) *domain.Play {
	cp := *p
	if p.Steps != nil {
		cp.Steps = append([]domain.PlayStep(nil), p.Steps...)
	}
	if p.CastAccountIDs != nil {
		cp.CastAccountIDs = append([]string(nil), p.CastAccountIDs...)
	}
	if p.TargetExternal != nil {
		te := *p.TargetExternal
		cp.TargetExternal = &te
	}
	return &cp
}

func (s *MemoryStore) SaveOutbox(_ context.Context, b *domain.OutboxBundle) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	cp := *b
	if b.Steps != nil {
		cp.Steps = append([]domain.OutboxStep(nil), b.Steps...)
	}
	s.outbox[b.ID] = &cp
	return nil
}

func (s *MemoryStore) GetOutbox(_ context.Context, id string) (*domain.OutboxBundle, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	b, ok := s.outbox[id]
	if !ok {
		return nil, domain.ErrNotFound
	}
	return copyOutbox(b), nil
}

func (s *MemoryStore) ListOutbox(_ context.Context, ownerUID int64) ([]*domain.OutboxBundle, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	var out []*domain.OutboxBundle
	for _, b := range s.outbox {
		if b.OwnerUID == ownerUID {
			out = append(out, copyOutbox(b))
		}
	}
	sort.Slice(out, func(i, j int) bool { return out[i].UpdatedAt > out[j].UpdatedAt })
	return out, nil
}

func (s *MemoryStore) DeleteOutbox(_ context.Context, id string) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	b, ok := s.outbox[id]
	if !ok {
		return domain.ErrNotFound
	}
	for _, step := range b.Steps {
		if step.Status == domain.StepPublishing {
			return domain.ErrIllegalStatus
		}
	}
	delete(s.outbox, id)
	return nil
}

func (s *MemoryStore) ClaimOutboxStep(_ context.Context, bundleID, stepID, leaseOwner string, now, leaseExpiresAt int64) (*domain.OutboxBundle, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	b, ok := s.outbox[bundleID]
	if !ok {
		return nil, domain.ErrNotFound
	}
	for i := range b.Steps {
		step := &b.Steps[i]
		if step.ID != stepID {
			continue
		}
		due := step.Status == domain.StepScheduled && step.ScheduledAt <= now
		stale := step.Status == domain.StepPublishing && step.LeaseExpiresAt <= now
		if !due && !stale {
			return nil, domain.ErrIllegalStatus
		}
		step.Status = domain.StepPublishing
		step.Error = ""
		step.LeaseOwner = leaseOwner
		step.LeaseExpiresAt = leaseExpiresAt
		b.Status = domain.OBActive
		b.UpdatedAt = now
		return copyOutbox(b), nil
	}
	return nil, domain.ErrNotFound
}

func (s *MemoryStore) RenewOutboxStepLease(_ context.Context, bundleID, stepID, leaseOwner string, now, leaseExpiresAt int64) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	b, ok := s.outbox[bundleID]
	if !ok {
		return domain.ErrNotFound
	}
	for i := range b.Steps {
		step := &b.Steps[i]
		if step.ID == stepID && step.Status == domain.StepPublishing && step.LeaseOwner == leaseOwner && step.LeaseExpiresAt > now {
			step.LeaseExpiresAt = leaseExpiresAt
			b.UpdatedAt = now
			return nil
		}
	}
	return domain.ErrLeaseLost
}

func (s *MemoryStore) FinishOutboxStep(_ context.Context, bundleID, stepID, leaseOwner string, result *domain.OutboxStep, now int64) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	b, ok := s.outbox[bundleID]
	if !ok {
		return domain.ErrNotFound
	}
	for i := range b.Steps {
		step := &b.Steps[i]
		if step.ID != stepID || step.Status != domain.StepPublishing || step.LeaseOwner != leaseOwner || step.LeaseExpiresAt <= now {
			continue
		}
		*step = *result
		step.LeaseOwner = ""
		step.LeaseExpiresAt = 0
		if step.Kind == domain.StepRoot {
			for j := range b.Steps {
				reply := &b.Steps[j]
				if reply.Kind != domain.StepReply || reply.Status == domain.StepPublished || reply.Status == domain.StepPublishing {
					continue
				}
				if step.Status == domain.StepFailed {
					reply.Status, reply.Error = domain.StepBlocked, "waiting for root success"
				} else if step.Status == domain.StepPublished && reply.Status == domain.StepBlocked {
					reply.Status, reply.Error = domain.StepScheduled, ""
				}
			}
		}
		b.UpdatedAt = now
		return nil
	}
	return domain.ErrLeaseLost
}

func (s *MemoryStore) RecomputeOutboxStatus(_ context.Context, bundleID string, now int64) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	b, ok := s.outbox[bundleID]
	if !ok {
		return domain.ErrNotFound
	}
	b.Status = outboxStatus(b.Steps)
	b.UpdatedAt = now
	return nil
}

func (s *MemoryStore) RetryOutboxStep(_ context.Context, bundleID, stepID string, now int64) (*domain.OutboxBundle, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	b, ok := s.outbox[bundleID]
	if !ok {
		return nil, domain.ErrNotFound
	}
	for i := range b.Steps {
		step := &b.Steps[i]
		if step.ID != stepID {
			continue
		}
		if step.Status != domain.StepFailed {
			return nil, domain.ErrIllegalStatus
		}
		step.Status, step.Error, step.ScheduledAt = domain.StepScheduled, "", now
		step.LeaseOwner, step.LeaseExpiresAt = "", 0
		b.Status, b.UpdatedAt = domain.OBScheduling, now
		return copyOutbox(b), nil
	}
	return nil, domain.ErrNotFound
}

func outboxStatus(steps []domain.OutboxStep) string {
	if len(steps) == 0 {
		return domain.OBCancelled
	}
	allPublished, anyFailed, anyPublishing := true, false, false
	for _, step := range steps {
		allPublished = allPublished && step.Status == domain.StepPublished
		anyFailed = anyFailed || step.Status == domain.StepFailed
		anyPublishing = anyPublishing || step.Status == domain.StepPublishing
	}
	if allPublished {
		return domain.OBCompleted
	}
	if anyFailed {
		return domain.OBPartial
	}
	if anyPublishing {
		return domain.OBActive
	}
	return domain.OBScheduling
}

func (s *MemoryStore) ListAllOutbox(_ context.Context) ([]*domain.OutboxBundle, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	var out []*domain.OutboxBundle
	for _, b := range s.outbox {
		out = append(out, copyOutbox(b))
	}
	return out, nil
}

func copyOutbox(b *domain.OutboxBundle) *domain.OutboxBundle {
	cp := *b
	if b.Steps != nil {
		cp.Steps = append([]domain.OutboxStep(nil), b.Steps...)
	}
	return &cp
}

func (s *MemoryStore) SaveOwnPost(_ context.Context, p *domain.OwnPost) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	cp := *p
	if p.Replies != nil {
		cp.Replies = append([]domain.OwnPostReply(nil), p.Replies...)
	}
	s.ownPosts[p.ID] = &cp
	return nil
}

func (s *MemoryStore) GetOwnPost(_ context.Context, id string) (*domain.OwnPost, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	p, ok := s.ownPosts[id]
	if !ok {
		return nil, domain.ErrNotFound
	}
	cp := *p
	if p.Replies != nil {
		cp.Replies = append([]domain.OwnPostReply(nil), p.Replies...)
	}
	return &cp, nil
}

func (s *MemoryStore) DeleteOwnPost(_ context.Context, id string) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	delete(s.ownPosts, id)
	return nil
}

func (s *MemoryStore) ListOwnPosts(_ context.Context, ownerUID int64, accountID string) ([]*domain.OwnPost, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	var out []*domain.OwnPost
	for _, p := range s.ownPosts {
		if p.OwnerUID != ownerUID {
			continue
		}
		if accountID != "" && p.AccountID != accountID {
			continue
		}
		cp := *p
		if p.Replies != nil {
			cp.Replies = append([]domain.OwnPostReply(nil), p.Replies...)
		}
		out = append(out, &cp)
	}
	sort.Slice(out, func(i, j int) bool { return out[i].PublishedAt > out[j].PublishedAt })
	return out, nil
}

func (s *MemoryStore) GetSyncMeta(_ context.Context, ownerUID int64) (*domain.SyncMeta, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	m, ok := s.syncMeta[ownerUID]
	if !ok {
		return &domain.SyncMeta{OwnerUID: ownerUID}, nil
	}
	cp := *m
	return &cp, nil
}

func (s *MemoryStore) SetSyncMeta(_ context.Context, m *domain.SyncMeta) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	cp := *m
	s.syncMeta[m.OwnerUID] = &cp
	return nil
}

func (s *MemoryStore) SaveMention(_ context.Context, m *domain.Mention) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	cp := *m
	s.mentions[m.ID] = &cp
	return nil
}

func (s *MemoryStore) GetMention(_ context.Context, id string) (*domain.Mention, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	m, ok := s.mentions[id]
	if !ok {
		return nil, domain.ErrNotFound
	}
	cp := *m
	return &cp, nil
}

func (s *MemoryStore) ListMentions(_ context.Context, ownerUID int64, accountID string) ([]*domain.Mention, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	var out []*domain.Mention
	for _, m := range s.mentions {
		if m.OwnerUID != ownerUID {
			continue
		}
		if accountID != "" && m.AccountID != accountID {
			continue
		}
		cp := *m
		out = append(out, &cp)
	}
	sort.Slice(out, func(i, j int) bool { return out[i].CreatedAt > out[j].CreatedAt })
	return out, nil
}

var _ domain.Repository = (*MemoryStore)(nil)
