package api

import (
	"encoding/json"
	"io"
	"log"
	"net/http"
	"sync"
	"time"

	"crypto-miner-server/internal/db"
	"crypto-miner-server/internal/ollama"
)

// ──────────────────────────────────────────────
//  AI Autonomy Handler
//  ──────────────────────────────────────────────
//  Provides REST endpoints for the AI Autonomy feature.
//  Agents call these endpoints to get decisions from Ollama
//  and report tool execution results.
// ──────────────────────────────────────────────

// AIHandler manages AI autonomy endpoints.
type AIHandler struct {
	db       *db.Database
	engines  map[string]*agentEngine // agentID -> engine wrapper
	reports  []ollama.Report         // recent tool execution reports
	activity map[string]AIActivityEntry
	onEvent  func(AIActivityEntry)
	mu       sync.RWMutex
}

type agentEngine struct {
	engine   *ollama.Engine
	lastUsed time.Time
}

// AIActivityEntry summarizes recent AI cycles per agent.
type AIActivityEntry struct {
	AgentID       string    `json:"agent_id"`
	LastDecideAt  time.Time `json:"last_decide_at,omitempty"`
	LastAction    string    `json:"last_action,omitempty"`
	LastTool      string    `json:"last_tool,omitempty"`
	ToolCallCount int       `json:"tool_call_count"`
	LastReasoning string    `json:"last_reasoning,omitempty"`
	LastReportAt  time.Time `json:"last_report_at,omitempty"`
	LastSuccess   bool      `json:"last_success"`
}

// NewAIHandler creates a new AI handler.
func NewAIHandler(database *db.Database) *AIHandler {
	h := &AIHandler{
		db:       database,
		engines:  make(map[string]*agentEngine),
		reports:  make([]ollama.Report, 0, 1000),
		activity: make(map[string]AIActivityEntry),
	}
	go h.runCleanupLoop()
	return h
}

func (h *AIHandler) runCleanupLoop() {
	ticker := time.NewTicker(10 * time.Minute)
	defer ticker.Stop()
	for range ticker.C {
		h.mu.Lock()
		now := time.Now()
		for id, eng := range h.engines {
			if now.Sub(eng.lastUsed) > 1*time.Hour {
				delete(h.engines, id)
			}
		}
		for id, act := range h.activity {
			if now.Sub(act.LastReportAt) > 24*time.Hour && now.Sub(act.LastDecideAt) > 24*time.Hour {
				delete(h.activity, id)
			}
		}
		h.mu.Unlock()
	}
}

func (h *AIHandler) SetEventBroadcaster(fn func(AIActivityEntry)) {
	h.mu.Lock()
	h.onEvent = fn
	h.mu.Unlock()
}

// SetEngineForAgent sets or updates the Ollama engine for a specific agent.
// This is called when an agent authenticates with AI settings.
func (h *AIHandler) SetEngineForAgent(agentID, ollamaEndpoint, model string) {
	h.mu.Lock()
	defer h.mu.Unlock()

	if ollamaEndpoint == "" {
		ollamaEndpoint = "http://localhost:11434"
	}
	if model == "" {
		model = "llama3.2"
	}

	h.engines[agentID] = &agentEngine{
		engine:   ollama.NewEngine(ollamaEndpoint, model),
		lastUsed: time.Now(),
	}
	log.Printf("[AI] Engine set for agent %s (endpoint=%s, model=%s)", agentID, ollamaEndpoint, model)
}

// RemoveEngine removes an agent's engine (on disconnect).
func (h *AIHandler) RemoveEngine(agentID string) {
	h.mu.Lock()
	defer h.mu.Unlock()
	delete(h.engines, agentID)
}

// GetEngine returns the engine for an agent.
func (h *AIHandler) GetEngine(agentID string) *ollama.Engine {
	h.mu.Lock()
	defer h.mu.Unlock()
	if entry, ok := h.engines[agentID]; ok {
		entry.lastUsed = time.Now()
		return entry.engine
	}
	return nil
}

// HandleDecide handles POST /api/v1/agent/decide
func (h *AIHandler) HandleDecide(w http.ResponseWriter, r *http.Request) {
	h.handleDecide(w, r)
}

// HandleReport handles POST /api/v1/agent/report
func (h *AIHandler) HandleReport(w http.ResponseWriter, r *http.Request) {
	h.handleReport(w, r)
}

// HandleHeartbeat handles POST /api/v1/agent/heartbeat
func (h *AIHandler) HandleHeartbeat(w http.ResponseWriter, r *http.Request) {
	h.handleHeartbeat(w, r)
}

// ─── Decide ───────────────────────────────────

// decideRequest carries the agent state for an AI decision cycle.
// OllamaEndpoint and Model are intentionally ignored on the server side —
// the engine endpoint is set server-side when the agent authenticates via WS
// to prevent SSRF via caller-supplied URLs.
type decideRequest struct {
	AgentID string `json:"agent_id"`
	// OllamaEndpoint is accepted in the payload for forward-compat but NEVER used;
	// the server uses only the endpoint set during WS authentication.
	OllamaEndpoint string `json:"ollama_endpoint,omitempty"`
	Model          string `json:"model,omitempty"`
	ollama.AgentState
}

func (h *AIHandler) handleDecide(w http.ResponseWriter, r *http.Request) {
	var req decideRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "invalid request: "+err.Error(), http.StatusBadRequest)
		return
	}

	if req.AgentID == "" {
		http.Error(w, "agent_id is required", http.StatusBadRequest)
		return
	}

	// Only use the engine that was registered when the agent authenticated via
	// WebSocket — do NOT create a new engine from caller-supplied OllamaEndpoint,
	// which would allow SSRF by pointing the server at an internal URL.
	engine := h.GetEngine(req.AgentID)
	if engine == nil {
		// Agent has not yet authenticated via WebSocket; reject to prevent
		// unauthenticated callers from triggering outbound Ollama requests.
		http.Error(w, "agent not registered — authenticate via WebSocket first", http.StatusForbidden)
		return
	}

	// Call Ollama for decision
	resp, err := engine.Decide(&req.AgentState)
	if err != nil {
		log.Printf("[AI] Decide error for agent %s: %v", req.AgentID, err)
		writeJSON(w, map[string]interface{}{
			"error": err.Error(),
			"tool_calls": []ollama.ToolCall{
				{
					Tool:   "sleep",
					Args:   map[string]string{"seconds": "120"}, // Should be parsed by agent to include random jitter
					Reason: "Ollama decision failed, backing off for 120 seconds to prevent thundering herd",
				},
			},
		})
		return
	}

	log.Printf("[AI] Agent %s decision: %s (%d tool calls)", req.AgentID, resp.Reasoning, len(resp.ToolCalls))

	lastAction := "decide"
	lastTool := ""
	if len(resp.ToolCalls) > 0 {
		lastTool = resp.ToolCalls[0].Tool
		lastAction = resp.ToolCalls[0].Tool
	}
	h.recordActivity(AIActivityEntry{
		AgentID:       req.AgentID,
		LastDecideAt:  time.Now(),
		LastAction:    lastAction,
		LastTool:      lastTool,
		ToolCallCount: len(resp.ToolCalls),
		LastReasoning: truncateStr(resp.Reasoning, 120),
	})

	writeJSON(w, resp)
}

// ─── Report ───────────────────────────────────

func (h *AIHandler) handleReport(w http.ResponseWriter, r *http.Request) {
	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "invalid report body", http.StatusBadRequest)
		return
	}

	var reports []ollama.Report
	if err := json.Unmarshal(body, &reports); err != nil {
		var single ollama.Report
		if err2 := json.Unmarshal(body, &single); err2 != nil {
			http.Error(w, "invalid report: "+err.Error(), http.StatusBadRequest)
			return
		}
		reports = []ollama.Report{single}
	}

	h.mu.Lock()
	for _, report := range reports {
		report.Timestamp = time.Now()
		h.reports = append(h.reports, report)
		log.Printf("[AI] Report from agent %s: tool=%s success=%v output=%s",
			report.AgentID, report.Tool, report.Success, truncateStr(report.Output, 200))

		prev := h.activity[report.AgentID]
		prev.AgentID = report.AgentID
		prev.LastReportAt = report.Timestamp
		prev.LastTool = report.Tool
		prev.LastAction = report.Tool
		prev.LastSuccess = report.Success
		h.activity[report.AgentID] = prev
		if h.onEvent != nil {
			h.onEvent(prev)
		}
	}
	// Keep only last 1000 reports
	if len(h.reports) > 1000 {
		h.reports = h.reports[len(h.reports)-1000:]
	}
	h.mu.Unlock()

	writeJSON(w, map[string]interface{}{
		"success":  true,
		"received": len(reports),
	})
}

// ─── Heartbeat ────────────────────────────────

type heartbeatRequest struct {
	AgentID string `json:"agent_id"`
	Status  string `json:"status"` // "alive", "restarting", "error"
	Message string `json:"message,omitempty"`
}

func (h *AIHandler) handleHeartbeat(w http.ResponseWriter, r *http.Request) {
	var req heartbeatRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "invalid heartbeat", http.StatusBadRequest)
		return
	}

	if req.AgentID == "" {
		http.Error(w, "agent_id is required", http.StatusBadRequest)
		return
	}

	log.Printf("[AI] Heartbeat from agent %s: status=%s", req.AgentID, req.Status)

	writeJSON(w, map[string]interface{}{
		"success":  true,
		"interval": 60, // seconds until next heartbeat
	})
}

// ─── Helpers ──────────────────────────────────

func truncateStr(s string, maxLen int) string {
	if len(s) <= maxLen {
		return s
	}
	return s[:maxLen] + "..."
}

func (h *AIHandler) recordActivity(entry AIActivityEntry) {
	h.mu.Lock()
	prev := h.activity[entry.AgentID]
	if !entry.LastDecideAt.IsZero() {
		prev.LastDecideAt = entry.LastDecideAt
	}
	if entry.LastAction != "" {
		prev.LastAction = entry.LastAction
	}
	if entry.LastTool != "" {
		prev.LastTool = entry.LastTool
	}
	if entry.ToolCallCount > 0 {
		prev.ToolCallCount = entry.ToolCallCount
	}
	if entry.LastReasoning != "" {
		prev.LastReasoning = entry.LastReasoning
	}
	prev.AgentID = entry.AgentID
	h.activity[entry.AgentID] = prev
	fn := h.onEvent
	h.mu.Unlock()
	if fn != nil {
		fn(prev)
	}
}

func (h *AIHandler) ActivitySnapshot() []AIActivityEntry {
	h.mu.RLock()
	defer h.mu.RUnlock()
	out := make([]AIActivityEntry, 0, len(h.activity))
	for _, v := range h.activity {
		out = append(out, v)
	}
	return out
}
