package backbeat

import (
	"crypto/sha256"
	"fmt"
	"time"
)

// BeatFrame represents the INT-A specification for BACKBEAT-REQ-002
// BACKBEAT-REQ-002: BeatFrame must emit INT-A with hlc, beat_index, downbeat, phase, deadline_at, tempo_bpm
type BeatFrame struct {
	Type       string    `json:"type"`        // INT-A: always "backbeat.beatframe.v1"
	ClusterID  string    `json:"cluster_id"`  // INT-A: cluster identifier
	BeatIndex  int64     `json:"beat_index"`  // INT-A: global beat counter (not cyclic)
	Downbeat   bool      `json:"downbeat"`    // INT-A: true when beat_index % bar_length == 1
	Phase      string    `json:"phase"`       // INT-A: current phase name
	HLC        string    `json:"hlc"`         // INT-A: hybrid logical clock timestamp
	DeadlineAt time.Time `json:"deadline_at"` // INT-A: RFC3339 timestamp for beat deadline
	TempoBPM   int       `json:"tempo_bpm"`   // INT-A: current tempo in beats per minute
	WindowID   string    `json:"window_id"`   // BACKBEAT-REQ-005: deterministic window identifier
}

// StatusClaim represents the INT-B specification for BACKBEAT-REQ-020
// BACKBEAT-REQ-020: StatusClaim must include type, agent_id, task_id, beat_index, state, beats_left, progress, notes, hlc
type StatusClaim struct {
	Type      string   `json:"type"`               // INT-B: always "backbeat.statusclaim.v1"
	AgentID   string   `json:"agent_id"`           // INT-B: agent identifier (e.g., "agent:xyz")
	TaskID    string   `json:"task_id"`            // INT-B: task identifier (e.g., "task:123")
	BeatIndex int64    `json:"beat_index"`         // INT-B: current beat index
	State     string   `json:"state"`              // INT-B: executing|planning|waiting|review|done|failed
	WaitFor   []string `json:"wait_for,omitempty"` // refs (e.g., hmmm://thread/...)
	BeatsLeft int      `json:"beats_left"`         // INT-B: estimated beats remaining
	Progress  float64  `json:"progress"`           // INT-B: progress ratio (0.0-1.0)
	Notes     string   `json:"notes"`              // INT-B: status description
	HLC       string   `json:"hlc"`                // INT-B: hybrid logical clock timestamp
}

// BarReport represents the INT-C specification for BACKBEAT-REQ-021
// BACKBEAT-REQ-021: BarReport must emit INT-C with window_id, from_beat, to_beat, and KPIs at each downbeat
type BarReport struct {
	Type                  string   `json:"type"`                    // INT-C: always "backbeat.barreport.v1"
	WindowID              string   `json:"window_id"`               // INT-C: deterministic window identifier
	FromBeat              int64    `json:"from_beat"`               // INT-C: starting beat index of the window
	ToBeat                int64    `json:"to_beat"`                 // INT-C: ending beat index of the window
	AgentsReporting       int      `json:"agents_reporting"`        // INT-C: number of unique agents that reported
	OnTimeReviews         int      `json:"on_time_reviews"`         // INT-C: tasks completed by deadline
	HelpPromisesFulfilled int      `json:"help_promises_fulfilled"` // INT-C: help requests fulfilled
	SecretRotationsOK     bool     `json:"secret_rotations_ok"`     // INT-C: security rotation status
	TempoDriftMS          int      `json:"tempo_drift_ms"`          // INT-C: tempo drift in milliseconds
	Issues                []string `json:"issues"`                  // INT-C: list of detected issues

	// Internal fields for aggregation (not part of INT-C)
	ClusterID   string         `json:"cluster_id,omitempty"`   // For internal routing
	StateCounts map[string]int `json:"state_counts,omitempty"` // For debugging
}

// PulseState represents the internal state of the pulse service
type PulseState struct {
	ClusterID    string
	NodeID       string
	IsLeader     bool
	BeatIndex    int64
	TempoBPM     int
	PendingBPM   int
	BarLength    int
	Phases       []string
	CurrentPhase int
	LastDownbeat time.Time
	StartTime    time.Time
	FrozenBeats  int
}

// TempoChangeRequest represents a tempo change request with validation
type TempoChangeRequest struct {
	TempoBPM      int    `json:"tempo_bpm"`
	Justification string `json:"justification,omitempty"`
}

// GenerateWindowID creates a deterministic window ID per BACKBEAT-REQ-005
// BACKBEAT-REQ-005: window_id = hex(sha256(cluster_id + ":" + downbeat_beat_index))[0:32]
func GenerateWindowID(clusterID string, downbeatBeatIndex int64) string {
	input := fmt.Sprintf("%s:%d", clusterID, downbeatBeatIndex)
	hash := sha256.Sum256([]byte(input))
	return fmt.Sprintf("%x", hash)[:32]
}

// IsDownbeat determines if a given beat index represents a downbeat
func IsDownbeat(beatIndex int64, barLength int) bool {
	return (beatIndex-1)%int64(barLength) == 0
}

// GetDownbeatIndex calculates the downbeat index for a given beat
func GetDownbeatIndex(beatIndex int64, barLength int) int64 {
	return ((beatIndex-1)/int64(barLength))*int64(barLength) + 1
}

// ValidateTempoChange checks if a tempo change is within acceptable limits
// BACKBEAT-REQ-004: Changes only on next downbeat; ≤±10% delta cap
func ValidateTempoChange(currentBPM, newBPM int) error {
	if newBPM <= 0 {
		return fmt.Errorf("invalid tempo: must be positive, got %d", newBPM)
	}

	// Calculate percentage change
	delta := float64(newBPM-currentBPM) / float64(currentBPM)
	maxDelta := 0.10 // 10% as per BACKBEAT-REQ-004

	if delta > maxDelta || delta < -maxDelta {
		return fmt.Errorf("tempo change exceeds ±10%% limit: current=%d new=%d delta=%.1f%%",
			currentBPM, newBPM, delta*100)
	}

	return nil
}

// ValidateStatusClaim validates a StatusClaim according to INT-B specification
func ValidateStatusClaim(sc *StatusClaim) error {
	if sc.Type != "backbeat.statusclaim.v1" {
		return fmt.Errorf("invalid type: expected 'backbeat.statusclaim.v1', got '%s'", sc.Type)
	}

	if sc.AgentID == "" {
		return fmt.Errorf("agent_id is required")
	}

	if sc.TaskID == "" {
		return fmt.Errorf("task_id is required")
	}

	if sc.BeatIndex <= 0 {
		return fmt.Errorf("beat_index must be positive, got %d", sc.BeatIndex)
	}

	validStates := map[string]bool{
		"executing": true,
		"planning":  true,
		"waiting":   true,
		"review":    true,
		"done":      true,
		"failed":    true,
	}

	if !validStates[sc.State] {
		return fmt.Errorf("invalid state: must be one of [executing, planning, waiting, review, done, failed], got '%s'", sc.State)
	}

	if sc.Progress < 0.0 || sc.Progress > 1.0 {
		return fmt.Errorf("progress must be between 0.0 and 1.0, got %f", sc.Progress)
	}

	if sc.HLC == "" {
		return fmt.Errorf("hlc is required")
	}

	return nil
}

// WindowAggregation represents aggregated data for a window
type WindowAggregation struct {
	WindowID       string
	FromBeat       int64
	ToBeat         int64
	Claims         []*StatusClaim
	AgentStates    map[string]string // agent_id -> latest state
	UniqueAgents   map[string]bool   // set of agent_ids that reported
	StateCounts    map[string]int    // state -> count
	CompletedTasks int               // tasks with state "done"
	FailedTasks    int               // tasks with state "failed"
	LastUpdated    time.Time
}

// NewWindowAggregation creates a new window aggregation
func NewWindowAggregation(windowID string, fromBeat, toBeat int64) *WindowAggregation {
	return &WindowAggregation{
		WindowID:     windowID,
		FromBeat:     fromBeat,
		ToBeat:       toBeat,
		Claims:       make([]*StatusClaim, 0),
		AgentStates:  make(map[string]string),
		UniqueAgents: make(map[string]bool),
		StateCounts:  make(map[string]int),
		LastUpdated:  time.Now(),
	}
}

// AddClaim adds a status claim to the window aggregation
func (wa *WindowAggregation) AddClaim(claim *StatusClaim) {
	wa.Claims = append(wa.Claims, claim)
	wa.UniqueAgents[claim.AgentID] = true

	// Update agent's latest state
	wa.AgentStates[claim.AgentID] = claim.State

	// Update state counts
	wa.StateCounts[claim.State]++

	// Track completed and failed tasks
	if claim.State == "done" {
		wa.CompletedTasks++
	} else if claim.State == "failed" {
		wa.FailedTasks++
	}

	wa.LastUpdated = time.Now()
}

// GenerateBarReport generates a BarReport from the aggregated data
func (wa *WindowAggregation) GenerateBarReport(clusterID string) *BarReport {
	// Calculate KPIs based on aggregated data
	agentsReporting := len(wa.UniqueAgents)
	onTimeReviews := wa.StateCounts["done"] // Tasks completed successfully

	// Help promises fulfilled - placeholder calculation
	// In a real implementation, this would track help request/response pairs
	helpPromisesFulfilled := wa.StateCounts["done"] / 10 // Rough estimate

	// Secret rotations OK - placeholder
	// In a real implementation, this would check security rotation status
	secretRotationsOK := true

	// Tempo drift - placeholder calculation
	// In a real implementation, this would measure actual tempo drift
	tempoDriftMS := 0

	// Detect issues based on aggregated data
	issues := make([]string, 0)
	if wa.FailedTasks > 0 {
		issues = append(issues, fmt.Sprintf("%d failed tasks detected", wa.FailedTasks))
	}

	if agentsReporting == 0 {
		issues = append(issues, "no agents reporting in window")
	}

	return &BarReport{
		Type:                  "backbeat.barreport.v1",
		WindowID:              wa.WindowID,
		FromBeat:              wa.FromBeat,
		ToBeat:                wa.ToBeat,
		AgentsReporting:       agentsReporting,
		OnTimeReviews:         onTimeReviews,
		HelpPromisesFulfilled: helpPromisesFulfilled,
		SecretRotationsOK:     secretRotationsOK,
		TempoDriftMS:          tempoDriftMS,
		Issues:                issues,
		ClusterID:             clusterID,
		StateCounts:           wa.StateCounts,
	}
}

// Score represents a YAML-based task score for agent simulation
type Score struct {
	Phases     map[string]int `yaml:"phases"`
	WaitBudget WaitBudget     `yaml:"wait_budget"`
}

// WaitBudget represents waiting time budgets for different scenarios
type WaitBudget struct {
	Help int `yaml:"help"`
}
