package approval

import (
	"fmt"
	"sync"
	"time"
)

// ApprovalStatus represents approval state
type ApprovalStatus string

const (
	StatusPending   ApprovalStatus = "pending"
	StatusApproved  ApprovalStatus = "approved"
	StatusRejected  ApprovalStatus = "rejected"
	StatusExpired   ApprovalStatus = "expired"
)

// ApprovalDecision represents an approval decision
type ApprovalDecision struct {
	Status     ApprovalStatus `json:"status"`
	ApprovedBy string         `json:"approved_by"`
	RejectedBy string         `json:"rejected_by"`
	Reason     string         `json:"reason"`
	Comments   string         `json:"comments"`
	Timestamp  time.Time      `json:"timestamp"`
	ExpiresAt  time.Time      `json:"expires_at"`
}

// ApprovalGate represents a human approval gate
type ApprovalGate struct {
	ID          string
	TaskID      string
	WorkflowID  string
	Description string
	Decision    *ApprovalDecision
	CreatedAt   time.Time
	TTL         time.Duration // Time until gate expires
	RequiredApprovals int      // Number of approvals needed (1 or more)
	Approvals   []string       // List of approvers
}

// ApprovalGateManager manages approval gates
type ApprovalGateManager struct {
	mu        sync.RWMutex
	gates     map[string]*ApprovalGate
	decisions map[string]*ApprovalDecision
	history   []*ApprovalRecord
	stats     *ApprovalStats
}

// ApprovalRecord tracks approval history
type ApprovalRecord struct {
	GateID    string
	Decision  ApprovalStatus
	ApprovedBy string
	RejectedBy string
	Timestamp time.Time
	Reason    string
}

// ApprovalStats tracks approval statistics
type ApprovalStats struct {
	TotalGates     int
	ApprovedGates  int
	RejectedGates  int
	PendingGates   int
	ExpiredGates   int
	AverageWaitTime time.Duration
}

// NewApprovalGateManager creates a new approval gate manager
func NewApprovalGateManager() *ApprovalGateManager {
	return &ApprovalGateManager{
		gates:     make(map[string]*ApprovalGate),
		decisions: make(map[string]*ApprovalDecision),
		history:   make([]*ApprovalRecord, 0),
		stats: &ApprovalStats{
			AverageWaitTime: 0,
		},
	}
}

// CreateGate creates a new approval gate
func (agm *ApprovalGateManager) CreateGate(taskID, workflowID, description string, ttl time.Duration) *ApprovalGate {
	if ttl == 0 {
		ttl = 24 * time.Hour // Default 24 hours
	}

	gate := &ApprovalGate{
		ID:          fmt.Sprintf("gate-%d", time.Now().UnixNano()),
		TaskID:      taskID,
		WorkflowID:  workflowID,
		Description: description,
		CreatedAt:   time.Now(),
		TTL:         ttl,
		RequiredApprovals: 1,
		Approvals:   make([]string, 0),
		Decision: &ApprovalDecision{
			Status:    StatusPending,
			ExpiresAt: time.Now().Add(ttl),
		},
	}

	agm.mu.Lock()
	defer agm.mu.Unlock()

	agm.gates[gate.ID] = gate
	agm.decisions[gate.ID] = gate.Decision
	agm.stats.TotalGates++
	agm.stats.PendingGates++

	return gate
}

// ApproveGate approves a gate
func (agm *ApprovalGateManager) ApproveGate(gateID, approvedBy, comments string) error {
	agm.mu.Lock()
	defer agm.mu.Unlock()

	gate, exists := agm.gates[gateID]
	if !exists {
		return fmt.Errorf("gate not found: %s", gateID)
	}

	if gate.Decision.Status == StatusApproved || gate.Decision.Status == StatusRejected {
		return fmt.Errorf("gate already has a decision: %s", gate.Decision.Status)
	}

	if time.Now().After(gate.Decision.ExpiresAt) {
		gate.Decision.Status = StatusExpired
		agm.stats.ExpiredGates++
		agm.stats.PendingGates--
		return fmt.Errorf("gate has expired")
	}

	gate.Decision.Status = StatusApproved
	gate.Decision.ApprovedBy = approvedBy
	gate.Decision.Comments = comments
	gate.Decision.Timestamp = time.Now()

	gate.Approvals = append(gate.Approvals, approvedBy)

	// Record in history
	record := &ApprovalRecord{
		GateID:     gateID,
		Decision:   StatusApproved,
		ApprovedBy: approvedBy,
		Timestamp:  gate.Decision.Timestamp,
		Reason:     comments,
	}

	agm.history = append(agm.history, record)
	agm.stats.ApprovedGates++
	agm.stats.PendingGates--

	return nil
}

// RejectGate rejects a gate
func (agm *ApprovalGateManager) RejectGate(gateID, rejectedBy, reason string) error {
	agm.mu.Lock()
	defer agm.mu.Unlock()

	gate, exists := agm.gates[gateID]
	if !exists {
		return fmt.Errorf("gate not found: %s", gateID)
	}

	if gate.Decision.Status == StatusApproved || gate.Decision.Status == StatusRejected {
		return fmt.Errorf("gate already has a decision: %s", gate.Decision.Status)
	}

	if time.Now().After(gate.Decision.ExpiresAt) {
		gate.Decision.Status = StatusExpired
		agm.stats.ExpiredGates++
		agm.stats.PendingGates--
		return fmt.Errorf("gate has expired")
	}

	gate.Decision.Status = StatusRejected
	gate.Decision.RejectedBy = rejectedBy
	gate.Decision.Reason = reason
	gate.Decision.Timestamp = time.Now()

	// Record in history
	record := &ApprovalRecord{
		GateID:    gateID,
		Decision:  StatusRejected,
		RejectedBy: rejectedBy,
		Timestamp: gate.Decision.Timestamp,
		Reason:    reason,
	}

	agm.history = append(agm.history, record)
	agm.stats.RejectedGates++
	agm.stats.PendingGates--

	return nil
}

// GetGate retrieves a gate
func (agm *ApprovalGateManager) GetGate(gateID string) (*ApprovalGate, bool) {
	agm.mu.RLock()
	defer agm.mu.RUnlock()

	gate, exists := agm.gates[gateID]
	return gate, exists
}

// GetDecision retrieves a decision
func (agm *ApprovalGateManager) GetDecision(gateID string) (*ApprovalDecision, bool) {
	agm.mu.RLock()
	defer agm.mu.RUnlock()

	decision, exists := agm.decisions[gateID]
	return decision, exists
}

// GetPendingGates returns all pending gates
func (agm *ApprovalGateManager) GetPendingGates() []*ApprovalGate {
	agm.mu.RLock()
	defer agm.mu.RUnlock()

	pending := make([]*ApprovalGate, 0)
	for _, gate := range agm.gates {
		if gate.Decision.Status == StatusPending {
			// Check if expired
			if time.Now().After(gate.Decision.ExpiresAt) {
				gate.Decision.Status = StatusExpired
			} else {
				pending = append(pending, gate)
			}
		}
	}

	return pending
}

// GetGatesByTask returns all gates for a task
func (agm *ApprovalGateManager) GetGatesByTask(taskID string) []*ApprovalGate {
	agm.mu.RLock()
	defer agm.mu.RUnlock()

	gates := make([]*ApprovalGate, 0)
	for _, gate := range agm.gates {
		if gate.TaskID == taskID {
			gates = append(gates, gate)
		}
	}

	return gates
}

// GetGatesByWorkflow returns all gates for a workflow
func (agm *ApprovalGateManager) GetGatesByWorkflow(workflowID string) []*ApprovalGate {
	agm.mu.RLock()
	defer agm.mu.RUnlock()

	gates := make([]*ApprovalGate, 0)
	for _, gate := range agm.gates {
		if gate.WorkflowID == workflowID {
			gates = append(gates, gate)
		}
	}

	return gates
}

// IsApproved checks if a gate is approved
func (agm *ApprovalGateManager) IsApproved(gateID string) bool {
	agm.mu.RLock()
	defer agm.mu.RUnlock()

	gate, exists := agm.gates[gateID]
	if !exists {
		return false
	}

	return gate.Decision.Status == StatusApproved
}

// IsRejected checks if a gate is rejected
func (agm *ApprovalGateManager) IsRejected(gateID string) bool {
	agm.mu.RLock()
	defer agm.mu.RUnlock()

	gate, exists := agm.gates[gateID]
	if !exists {
		return false
	}

	return gate.Decision.Status == StatusRejected
}

// IsPending checks if a gate is still pending
func (agm *ApprovalGateManager) IsPending(gateID string) bool {
	agm.mu.RLock()
	defer agm.mu.RUnlock()

	gate, exists := agm.gates[gateID]
	if !exists {
		return false
	}

	if time.Now().After(gate.Decision.ExpiresAt) {
		return false // Expired gates are not pending
	}

	return gate.Decision.Status == StatusPending
}

// GetStats returns statistics
func (agm *ApprovalGateManager) GetStats() *ApprovalStats {
	agm.mu.RLock()
	defer agm.mu.RUnlock()

	stats := *agm.stats
	return &stats
}

// GetHistory returns approval history
func (agm *ApprovalGateManager) GetHistory() []*ApprovalRecord {
	agm.mu.RLock()
	defer agm.mu.RUnlock()

	result := make([]*ApprovalRecord, len(agm.history))
	copy(result, agm.history)

	return result
}

// Clear clears all gates
func (agm *ApprovalGateManager) Clear() {
	agm.mu.Lock()
	defer agm.mu.Unlock()

	agm.gates = make(map[string]*ApprovalGate)
	agm.decisions = make(map[string]*ApprovalDecision)
	agm.history = make([]*ApprovalRecord, 0)
	agm.stats = &ApprovalStats{}
}
