package recovery

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"sync"
	"time"
)

// DeadletterItem represents a failed activity/task
type DeadletterItem struct {
	ID            string    `json:"id"`
	Type          string    `json:"type"` // "activity", "task", "workflow"
	WorkflowID    string    `json:"workflow_id"`
	Error         string    `json:"error"`
	LastAttempt   time.Time `json:"last_attempt"`
	AttemptCount  int       `json:"attempt_count"`
	MaxAttempts   int       `json:"max_attempts"`
	Data          any       `json:"data"` // Original input
	Recoverable   bool      `json:"recoverable"`
	RecoveryNote  string    `json:"recovery_note"`
	CreatedAt     time.Time `json:"created_at"`
	UpdatedAt     time.Time `json:"updated_at"`
}

// DeadletterQueue manages deadlettered items
type DeadletterQueue struct {
	mu    sync.RWMutex
	path  string
	items map[string]*DeadletterItem
}

// NewDeadletterQueue creates a new deadletter queue
func NewDeadletterQueue(path string) *DeadletterQueue {
	return &DeadletterQueue{
		path:  path,
		items: make(map[string]*DeadletterItem),
	}
}

// Add adds an item to the deadletter queue
func (dq *DeadletterQueue) Add(item *DeadletterItem) error {
	if item.ID == "" {
		return fmt.Errorf("deadletter item must have an ID")
	}

	dq.mu.Lock()
	defer dq.mu.Unlock()

	now := time.Now()
	if item.CreatedAt.IsZero() {
		item.CreatedAt = now
	}
	item.UpdatedAt = now

	dq.items[item.ID] = item

	// Persist to disk
	return dq.persistLocked()
}

// Get retrieves an item from the deadletter queue
func (dq *DeadletterQueue) Get(id string) *DeadletterItem {
	dq.mu.RLock()
	defer dq.mu.RUnlock()

	return dq.items[id]
}

// GetAll returns all deadletter items
func (dq *DeadletterQueue) GetAll() []*DeadletterItem {
	dq.mu.RLock()
	defer dq.mu.RUnlock()

	items := make([]*DeadletterItem, 0, len(dq.items))
	for _, item := range dq.items {
		items = append(items, item)
	}
	return items
}

// GetRecoverable returns all recoverable items
func (dq *DeadletterQueue) GetRecoverable() []*DeadletterItem {
	dq.mu.RLock()
	defer dq.mu.RUnlock()

	items := make([]*DeadletterItem, 0)
	for _, item := range dq.items {
		if item.Recoverable {
			items = append(items, item)
		}
	}
	return items
}

// Remove removes an item from the deadletter queue
func (dq *DeadletterQueue) Remove(id string) error {
	dq.mu.Lock()
	defer dq.mu.Unlock()

	delete(dq.items, id)
	return dq.persistLocked()
}

// Resolve marks an item as resolved
func (dq *DeadletterQueue) Resolve(id string, note string) error {
	dq.mu.Lock()
	defer dq.mu.Unlock()

	item, exists := dq.items[id]
	if !exists {
		return fmt.Errorf("item not found: %s", id)
	}

	item.RecoveryNote = note
	item.UpdatedAt = time.Now()

	// Don't actually delete, just mark as recovered
	// This maintains audit trail
	return dq.persistLocked()
}

// Load loads deadletter queue from disk
func (dq *DeadletterQueue) Load() error {
	dq.mu.Lock()
	defer dq.mu.Unlock()

	// Create directory if it doesn't exist
	if err := os.MkdirAll(filepath.Dir(dq.path), 0755); err != nil {
		return err
	}

	// If file doesn't exist, that's OK (queue is empty)
	data, err := os.ReadFile(dq.path)
	if err != nil {
		if os.IsNotExist(err) {
			return nil
		}
		return err
	}

	var items []*DeadletterItem
	if err := json.Unmarshal(data, &items); err != nil {
		return err
	}

	dq.items = make(map[string]*DeadletterItem)
	for _, item := range items {
		dq.items[item.ID] = item
	}

	return nil
}

// persistLocked persists the queue to disk (must be called with lock held)
func (dq *DeadletterQueue) persistLocked() error {
	items := make([]*DeadletterItem, 0, len(dq.items))
	for _, item := range dq.items {
		items = append(items, item)
	}

	data, err := json.MarshalIndent(items, "", "  ")
	if err != nil {
		return err
	}

	// Create directory if it doesn't exist
	if err := os.MkdirAll(filepath.Dir(dq.path), 0755); err != nil {
		return err
	}

	return os.WriteFile(dq.path, data, 0644)
}

// Count returns the number of items in the queue
func (dq *DeadletterQueue) Count() int {
	dq.mu.RLock()
	defer dq.mu.RUnlock()
	return len(dq.items)
}

// IsEmpty checks if the queue is empty
func (dq *DeadletterQueue) IsEmpty() bool {
	dq.mu.RLock()
	defer dq.mu.RUnlock()
	return len(dq.items) == 0
}

// CreateDeadletterItem creates a new deadletter item from an error
func CreateDeadletterItem(id, itemType, workflowID string, err error, data any, recoverable bool) *DeadletterItem {
	return &DeadletterItem{
		ID:           id,
		Type:         itemType,
		WorkflowID:   workflowID,
		Error:        err.Error(),
		LastAttempt:  time.Now(),
		AttemptCount: 1,
		MaxAttempts:  3,
		Data:         data,
		Recoverable:  recoverable,
		CreatedAt:    time.Now(),
		UpdatedAt:    time.Now(),
	}
}
