package p2p

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"github.com/google/uuid"
	"github.com/rs/zerolog/log"
)

// Broadcaster handles P2P broadcasting of opportunities and events to CHORUS agents
type Broadcaster struct {
	discovery *Discovery
	ctx       context.Context
	cancel    context.CancelFunc
}

// NewBroadcaster creates a new P2P broadcaster
func NewBroadcaster(discovery *Discovery) *Broadcaster {
	ctx, cancel := context.WithCancel(context.Background())

	return &Broadcaster{
		discovery: discovery,
		ctx:       ctx,
		cancel:    cancel,
	}
}

// Close shuts down the broadcaster
func (b *Broadcaster) Close() error {
	b.cancel()
	return nil
}

// CouncilOpportunity represents a council formation opportunity for agents to claim
type CouncilOpportunity struct {
	CouncilID         uuid.UUID              `json:"council_id"`
	ProjectName       string                 `json:"project_name"`
	Repository        string                 `json:"repository"`
	ProjectBrief      string                 `json:"project_brief"`
	CoreRoles         []CouncilRole          `json:"core_roles"`
	OptionalRoles     []CouncilRole          `json:"optional_roles"`
	UCXLAddress       string                 `json:"ucxl_address"`
	FormationDeadline time.Time              `json:"formation_deadline"`
	CreatedAt         time.Time              `json:"created_at"`
	Metadata          map[string]interface{} `json:"metadata"`
}

// CouncilRole represents a role within a council that can be claimed
type CouncilRole struct {
	RoleName       string   `json:"role_name"`
	AgentName      string   `json:"agent_name"`
	Required       bool     `json:"required"`
	RequiredSkills []string `json:"required_skills"`
	Description    string   `json:"description"`
}

// RoleCounts provides claimed vs total counts for a role category
type RoleCounts struct {
	Total   int `json:"total"`
	Claimed int `json:"claimed"`
}

// PersonaCounts captures persona readiness across council roles.
type PersonaCounts struct {
	Total      int `json:"total"`
	Loaded     int `json:"loaded"`
	CoreLoaded int `json:"core_loaded"`
}

// CouncilStatusUpdate notifies agents about council staffing progress
type CouncilStatusUpdate struct {
	CouncilID       uuid.UUID     `json:"council_id"`
	ProjectName     string        `json:"project_name"`
	Status          string        `json:"status"`
	Message         string        `json:"message,omitempty"`
	Timestamp       time.Time     `json:"timestamp"`
	CoreRoles       RoleCounts    `json:"core_roles"`
	Optional        RoleCounts    `json:"optional_roles"`
	Personas        PersonaCounts `json:"personas,omitempty"`
	BriefDispatched bool          `json:"brief_dispatched"`
}

// BroadcastCouncilOpportunity broadcasts a council formation opportunity to all available CHORUS agents
func (b *Broadcaster) BroadcastCouncilOpportunity(ctx context.Context, opportunity *CouncilOpportunity) error {
	log.Info().
		Str("council_id", opportunity.CouncilID.String()).
		Str("project_name", opportunity.ProjectName).
		Int("core_roles", len(opportunity.CoreRoles)).
		Int("optional_roles", len(opportunity.OptionalRoles)).
		Msg("📡 Broadcasting council opportunity to CHORUS agents")

	// Get all discovered agents
	agents := b.discovery.GetAgents()

	if len(agents) == 0 {
		log.Warn().Msg("No CHORUS agents discovered to broadcast opportunity to")
		return fmt.Errorf("no agents available to receive broadcast")
	}

	successCount := 0
	errorCount := 0

	// Broadcast to each agent
	for _, agent := range agents {
		err := b.sendOpportunityToAgent(ctx, agent, opportunity)
		if err != nil {
			log.Error().
				Err(err).
				Str("agent_id", agent.ID).
				Str("endpoint", agent.Endpoint).
				Msg("Failed to send opportunity to agent")
			errorCount++
			continue
		}
		successCount++
	}

	log.Info().
		Int("success_count", successCount).
		Int("error_count", errorCount).
		Int("total_agents", len(agents)).
		Msg("✅ Council opportunity broadcast completed")

	if successCount == 0 {
		return fmt.Errorf("failed to broadcast to any agents")
	}

	return nil
}

// sendOpportunityToAgent sends a council opportunity to a specific CHORUS agent
func (b *Broadcaster) sendOpportunityToAgent(ctx context.Context, agent *Agent, opportunity *CouncilOpportunity) error {
	// Construct the agent's opportunity endpoint
	// CHORUS agents should expose /api/v1/opportunities endpoint to receive opportunities
	opportunityURL := fmt.Sprintf("%s/api/v1/opportunities/council", agent.Endpoint)

	// Marshal opportunity to JSON
	payload, err := json.Marshal(opportunity)
	if err != nil {
		return fmt.Errorf("failed to marshal opportunity: %w", err)
	}

	// Create HTTP request
	req, err := http.NewRequestWithContext(ctx, "POST", opportunityURL, bytes.NewBuffer(payload))
	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-WHOOSH-Broadcast", "council-opportunity")
	req.Header.Set("X-Council-ID", opportunity.CouncilID.String())

	// Send request with timeout
	client := &http.Client{
		Timeout: 10 * time.Second,
	}

	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("failed to send opportunity to agent: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted {
		return fmt.Errorf("agent returned non-success status: %d", resp.StatusCode)
	}

	log.Debug().
		Str("agent_id", agent.ID).
		Str("council_id", opportunity.CouncilID.String()).
		Int("status_code", resp.StatusCode).
		Msg("Successfully sent council opportunity to agent")

	return nil
}

// BroadcastAgentAssignment notifies an agent that they've been assigned to a council role
func (b *Broadcaster) BroadcastAgentAssignment(ctx context.Context, agentID string, assignment *AgentAssignment) error {
	// Find the agent
	agents := b.discovery.GetAgents()
	var targetAgent *Agent

	for _, agent := range agents {
		if agent.ID == agentID {
			targetAgent = agent
			break
		}
	}

	if targetAgent == nil {
		return fmt.Errorf("agent %s not found in discovery", agentID)
	}

	// Send assignment to agent
	assignmentURL := fmt.Sprintf("%s/api/v1/assignments/council", targetAgent.Endpoint)

	payload, err := json.Marshal(assignment)
	if err != nil {
		return fmt.Errorf("failed to marshal assignment: %w", err)
	}

	req, err := http.NewRequestWithContext(ctx, "POST", assignmentURL, bytes.NewBuffer(payload))
	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-WHOOSH-Broadcast", "council-assignment")

	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("failed to send assignment to agent: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted {
		return fmt.Errorf("agent returned non-success status: %d", resp.StatusCode)
	}

	log.Info().
		Str("agent_id", agentID).
		Str("council_id", assignment.CouncilID.String()).
		Str("role", assignment.RoleName).
		Msg("✅ Successfully notified agent of council assignment")

	return nil
}

// BroadcastCouncilStatusUpdate notifies all discovered agents about council staffing status
func (b *Broadcaster) BroadcastCouncilStatusUpdate(ctx context.Context, update *CouncilStatusUpdate) error {
	log.Info().
		Str("council_id", update.CouncilID.String()).
		Str("status", update.Status).
		Msg("📢 Broadcasting council status update to CHORUS agents")

	agents := b.discovery.GetAgents()
	if len(agents) == 0 {
		log.Warn().Str("council_id", update.CouncilID.String()).Msg("No CHORUS agents discovered for council status update")
		return fmt.Errorf("no agents available to receive council status update")
	}

	successCount := 0
	errorCount := 0

	for _, agent := range agents {
		if err := b.sendCouncilStatusToAgent(ctx, agent, update); err != nil {
			log.Error().
				Err(err).
				Str("agent_id", agent.ID).
				Str("council_id", update.CouncilID.String()).
				Msg("Failed to send council status update to agent")
			errorCount++
			continue
		}
		successCount++
	}

	log.Info().
		Str("council_id", update.CouncilID.String()).
		Int("success_count", successCount).
		Int("error_count", errorCount).
		Int("total_agents", len(agents)).
		Msg("✅ Council status update broadcast completed")

	if successCount == 0 {
		return fmt.Errorf("failed to broadcast council status update to any agents")
	}

	return nil
}

func (b *Broadcaster) sendCouncilStatusToAgent(ctx context.Context, agent *Agent, update *CouncilStatusUpdate) error {
	statusURL := fmt.Sprintf("%s/api/v1/councils/status", agent.Endpoint)

	payload, err := json.Marshal(update)
	if err != nil {
		return fmt.Errorf("failed to marshal council status update: %w", err)
	}

	req, err := http.NewRequestWithContext(ctx, "POST", statusURL, bytes.NewBuffer(payload))
	if err != nil {
		return fmt.Errorf("failed to create council status request: %w", err)
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-WHOOSH-Broadcast", "council-status")
	req.Header.Set("X-Council-ID", update.CouncilID.String())

	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("failed to send council status to agent: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted {
		return fmt.Errorf("agent returned non-success status: %d", resp.StatusCode)
	}

	log.Debug().
		Str("agent_id", agent.ID).
		Str("council_id", update.CouncilID.String()).
		Int("status_code", resp.StatusCode).
		Msg("Successfully sent council status update to agent")

	return nil
}

// AgentAssignment represents an assignment of an agent to a council role
type AgentAssignment struct {
	CouncilID    uuid.UUID              `json:"council_id"`
	ProjectName  string                 `json:"project_name"`
	RoleName     string                 `json:"role_name"`
	UCXLAddress  string                 `json:"ucxl_address"`
	ProjectBrief string                 `json:"project_brief"`
	Repository   string                 `json:"repository"`
	AssignedAt   time.Time              `json:"assigned_at"`
	Metadata     map[string]interface{} `json:"metadata"`
}
