package api

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"os/exec"
	"strings"
	"sync/atomic"

	"github.com/muyue/muyue/internal/agent"
	"github.com/muyue/muyue/internal/config"
	"github.com/muyue/muyue/internal/installer"
	"github.com/muyue/muyue/internal/lessons"
	"github.com/muyue/muyue/internal/memory"
	"github.com/muyue/muyue/internal/mcpserver"
	"github.com/muyue/muyue/internal/plugins"
	"github.com/muyue/muyue/internal/rag"
	"github.com/muyue/muyue/internal/scanner"
	"github.com/muyue/muyue/internal/workflow"
)

type Server struct {
	config              *config.MuyueConfig
	scanResult          *scanner.ScanResult
	mux                 *http.ServeMux
	convStore           *ConversationStore
	shellConvStore      *ShellConvStore
	consumption         *consumptionStore
	agentRegistry       *agent.Registry
	agentToolsJSON      json.RawMessage
	shellAgentRegistry  *agent.Registry
	shellAgentToolsJSON json.RawMessage
	workflowEngine      *workflow.Engine
	pluginManager       *plugins.Manager
	hookRegistry        *plugins.HookRegistry
	browserTestStore    *BrowserTestStore
	memoryStore         *memory.Store
	ragStore            *rag.Store
	pipeline            *Pipeline
	activeCrushAgents   atomic.Int32
	activeClaudeAgents  atomic.Int32
	mcpServer           *mcpserver.MCPServer
	agentTracker        *AgentSessionTracker
}

func NewServer(cfg *config.MuyueConfig) *Server {
	s := &Server{
		mux: http.NewServeMux(),
	}
	// Auto-initialize config if nil or if no config file exists on disk
	if cfg == nil || !config.Exists() {
		defaultCfg := config.Default()
		if cfg != nil {
			// Preserve any user-provided settings from cfg
			defaultCfg.Profile = cfg.Profile
			defaultCfg.AI = cfg.AI
			defaultCfg.Tools = cfg.Tools
			defaultCfg.BMAD = cfg.BMAD
			defaultCfg.Terminal = cfg.Terminal
		}
		// Save initial config to establish the file for first-time usage
		if err := config.Save(defaultCfg); err != nil {
			_ = err
		}
		cfg = defaultCfg
	}
	s.config = cfg
	s.scanResult = scanner.ScanSystem()
	s.convStore = NewConversationStore()
	s.shellConvStore = NewShellConvStore()
	s.consumption = newConsumptionStore()
	s.agentRegistry = agent.DefaultRegistry()
	s.browserTestStore = NewBrowserTestStore()
	if err := RegisterBrowserTestTool(s.agentRegistry, s.browserTestStore); err != nil {
		// Tool registration only fails for duplicate names — non-fatal
		_ = err
	}
	tools := s.agentRegistry.OpenAITools()
	toolsJSON, _ := json.Marshal(tools)
	s.agentToolsJSON = json.RawMessage(toolsJSON)

	s.shellAgentRegistry = agent.NewRegistry()
	terminalTool, _ := agent.NewTerminalTool()
	s.shellAgentRegistry.Register(terminalTool)
	shellTools := s.shellAgentRegistry.OpenAITools()
	shellToolsJSON, _ := json.Marshal(shellTools)
	s.shellAgentToolsJSON = json.RawMessage(shellToolsJSON)

	s.workflowEngine, _ = workflow.NewEngine(s.agentRegistry)

	if cfg.Lessons.Enabled {
		lessons.EnsureBuiltinLessons()
	}

	s.hookRegistry = plugins.NewHookRegistry()
	s.pluginManager = plugins.NewManager(s.hookRegistry)

	pluginPaths := cfg.Plugins.Paths
	if len(pluginPaths) == 0 {
		pluginPaths = plugins.DefaultPluginPaths()
	}
	discovered := plugins.DiscoverPlugins(pluginPaths)
	for _, dp := range discovered {
		if dp.Valid {
			p, err := plugins.LoadExecutablePlugin(dp)
			if err == nil {
					s.pluginManager.Register(p)
			}
		}
	}
	s.pluginManager.EnableFromConfig(context.Background(), cfg.Plugins.Enabled, s.agentRegistry)

	s.pipeline = NewPipeline()

	s.agentTracker = NewAgentSessionTracker()

	s.initStarship()
	s.routes()
	return s
}

func (s *Server) routes() {
	s.mux.HandleFunc("/api/info", s.handleInfo)
	s.mux.HandleFunc("/api/system", s.handleSystem)
	s.mux.HandleFunc("/api/tools", s.handleTools)
	s.mux.HandleFunc("/api/config", s.handleConfig)
	s.mux.HandleFunc("/api/providers", s.handleProviders)
	s.mux.HandleFunc("/api/skills", s.handleSkills)
	s.mux.HandleFunc("/api/lsp", s.handleLSP)
	s.mux.HandleFunc("/api/mcp", s.handleMCP)
	s.mux.HandleFunc("/api/updates", s.handleUpdates)
	s.mux.HandleFunc("/api/install", s.handleInstall)
	s.mux.HandleFunc("/api/scan", s.handleScan)
	s.mux.HandleFunc("/api/editors", s.handleEditors)
	s.mux.HandleFunc("/api/preferences", s.handleUpdatePreferences)
	s.mux.HandleFunc("/api/terminal", s.handleTerminal)
	s.mux.HandleFunc("/api/ws/terminal", s.handleTerminalWS)
	s.mux.HandleFunc("/api/terminal/sessions", s.handleTerminalSessions)
	s.mux.HandleFunc("/api/terminal/sessions/", s.handleTerminalSessionsDelete)
	s.mux.HandleFunc("/api/terminal/themes", s.handleGetTerminalThemes)
	s.mux.HandleFunc("/api/terminal/settings", s.handleSaveTerminalSettings)
	s.mux.HandleFunc("/api/mcp/configure", s.handleMCPConfigure)
	s.mux.HandleFunc("/api/config/profile", s.handleSaveProfile)
	s.mux.HandleFunc("/api/config/provider", s.handleSaveProvider)
	s.mux.HandleFunc("/api/config/reset", s.handleResetConfig)
	s.mux.HandleFunc("/api/starship/apply-theme", s.handleApplyStarshipTheme)
	s.mux.HandleFunc("/api/providers/validate", s.handleValidateProvider)
	s.mux.HandleFunc("/api/update/run", s.handleRunUpdate)
	s.mux.HandleFunc("/api/images/generate", s.handleImageGenerate)
	s.mux.HandleFunc("/api/images/", s.handleServeImage)
	s.mux.HandleFunc("/api/chat", s.handleChat)
	s.mux.HandleFunc("/api/chat/history", s.handleChatHistory)
	s.mux.HandleFunc("/api/chat/clear", s.handleChatClear)
	s.mux.HandleFunc("/api/chat/summarize", s.handleChatSummarize)
	s.mux.HandleFunc("/api/tool/call", s.handleToolCall)
	s.mux.HandleFunc("/api/tools/list", s.handleToolList)
	s.mux.HandleFunc("/api/shell/chat", s.handleShellChat)
	s.mux.HandleFunc("/api/shell/chat/history", s.handleShellChatHistory)
	s.mux.HandleFunc("/api/shell/chat/clear", s.handleShellChatClear)
	s.mux.HandleFunc("/api/shell/analyze", s.handleShellAnalyze)
	s.mux.HandleFunc("/api/shell/analysis", s.handleShellAnalysisGet)
	s.mux.HandleFunc("/api/workflow", s.handleWorkflowCreate)
	s.mux.HandleFunc("/api/workflow/list", s.handleWorkflowList)
	s.mux.HandleFunc("/api/workflow/", s.handleWorkflowGet)
	s.mux.HandleFunc("/api/workflow/plan", s.handleWorkflowPlan)
	s.mux.HandleFunc("/api/workflow/execute/", s.handleWorkflowExecute)
	s.mux.HandleFunc("/api/workflow/approve/", s.handleWorkflowApprove)
	s.mux.HandleFunc("/api/conversations", s.handleListConversations)
	s.mux.HandleFunc("/api/conversations/search", s.handleSearchConversations)
	s.mux.HandleFunc("/api/conversations/export", s.handleExportConversation)
	s.mux.HandleFunc("/api/conversations/", s.handleDeleteConversation)
	s.mux.HandleFunc("/api/lsp/install", s.handleLSPInstall)
	s.mux.HandleFunc("/api/skills/deploy", s.handleSkillsDeploy)
	s.mux.HandleFunc("/api/skills/undeploy", s.handleSkillsUndeploy)
	s.mux.HandleFunc("/api/ssh/connections", s.handleSSHConnections)
	s.mux.HandleFunc("/api/ssh/test", s.handleSSHTest)

	s.mux.HandleFunc("/api/mcp/status", s.handleMCPStatus)
	s.mux.HandleFunc("/api/mcp/registry", s.handleMCPRegistry)
	s.mux.HandleFunc("/api/lsp/health", s.handleLSPHealth)
	s.mux.HandleFunc("/api/lsp/auto-install", s.handleLSPAutoInstall)
	s.mux.HandleFunc("/api/lsp/editor-config", s.handleLSPEditorConfig)
	s.mux.HandleFunc("/api/skills/validate", s.handleSkillValidate)
	s.mux.HandleFunc("/api/skills/test", s.handleSkillTest)
	s.mux.HandleFunc("/api/skills/export", s.handleSkillExport)
	s.mux.HandleFunc("/api/skills/import", s.handleSkillImport)
	s.mux.HandleFunc("/api/dashboard/status", s.handleDashboardStatus)
	s.mux.HandleFunc("/api/ai/task", s.handleAITask)
	s.mux.HandleFunc("/api/providers/quota", s.handleProvidersQuota)
	s.mux.HandleFunc("/api/providers/consumption", s.handleProvidersConsumption)
	s.mux.HandleFunc("/api/recent-commands", s.handleRecentCommands)
	s.mux.HandleFunc("/api/running-processes", s.handleRunningProcesses)
	s.mux.HandleFunc("/api/system/metrics", s.handleSystemMetrics)

	s.mux.HandleFunc("/api/test/snippet", s.handleBrowserTestSnippet)
	s.mux.HandleFunc("/api/test/sessions", s.handleBrowserTestSessions)
	s.mux.HandleFunc("/api/test/console/", s.handleBrowserTestConsole)
	s.mux.HandleFunc("/api/ws/browser-test", s.handleBrowserTestWS)

	s.mux.HandleFunc("/api/skills/auto-create", s.handleSkillAutoCreate)
	s.mux.HandleFunc("/api/skills/proposals", s.handleSkillProposals)
	s.mux.HandleFunc("/api/skills/detail/", s.handleSkillDetail)
	s.mux.HandleFunc("/api/plugins", s.handlePlugins)
	s.mux.HandleFunc("/api/plugins/", s.handlePluginAction)
	s.mux.HandleFunc("/api/lessons", s.handleLessons)
	s.mux.HandleFunc("/api/lessons/match", s.handleLessonsMatch)
	s.mux.HandleFunc("/api/mcp/discover", s.handleMCPDiscover)
	s.mux.HandleFunc("/api/browser/navigate", s.handleBrowserNavigate)
	s.mux.HandleFunc("/api/browser/screenshot", s.handleBrowserScreenshot)
	s.mux.HandleFunc("/api/browser/action", s.handleBrowserAction)

	s.mux.HandleFunc("/api/rag/index", s.handleRAGIndex)
	s.mux.HandleFunc("/api/rag/search", s.handleRAGSearch)
	s.mux.HandleFunc("/api/rag/status", s.handleRAGStatus)
	s.mux.HandleFunc("/api/rag/documents", s.handleRAGDocuments)
	s.mux.HandleFunc("/api/rag/index/", s.handleRAGDelete)
	s.mux.HandleFunc("/api/pipeline/filters", s.handlePipelineFilters)
	s.mux.HandleFunc("/api/pipeline/filters/", s.handlePipelineToggle)
	s.mux.HandleFunc("/api/memory", s.handleMemoryList)
	s.mux.HandleFunc("/api/memory/create", s.handleMemoryCreate)
	s.mux.HandleFunc("/api/memory/", s.handleMemoryOperation)
	s.mux.HandleFunc("/api/memory/search", s.handleMemorySearch)
	s.mux.HandleFunc("/api/memory/recall", s.handleMemoryRecall)
	s.mux.HandleFunc("/api/memory/context", s.handleMemoryContext)
	s.mux.HandleFunc("/api/files/content", s.handleFileContent)
	s.mux.HandleFunc("/api/mcp-server/status", s.handleMuyueMCPServerStatus)
	s.mux.HandleFunc("/api/mcp-server/start", s.handleMuyueMCPServerStart)
	s.mux.HandleFunc("/api/mcp-server/stop", s.handleMuyueMCPServerStop)
	s.mux.HandleFunc("/api/agent-sessions", s.handleAgentSessionsList)
	s.mux.HandleFunc("/api/agent-sessions/", s.handleAgentSessionOutput)
	s.mux.HandleFunc("/api/workspaces", s.handleWorkspaceList)
	s.mux.HandleFunc("/api/workspace", s.handleWorkspaceSave)
	s.mux.HandleFunc("/api/workspace/", s.handleWorkspaceGet)
}

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if strings.HasPrefix(r.URL.Path, "/api/ws/") || strings.HasPrefix(r.URL.Path, "/api/images/") {
		s.mux.ServeHTTP(w, r)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	if origin := r.Header.Get("Origin"); isAllowedOrigin(origin) {
		w.Header().Set("Access-Control-Allow-Origin", origin)
		w.Header().Set("Vary", "Origin")
	}
	w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
	w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
	if r.Method == "OPTIONS" {
		w.WriteHeader(http.StatusOK)
		return
	}
	s.mux.ServeHTTP(w, r)
}

func isAllowedOrigin(origin string) bool {
	if origin == "" {
		return false
	}
	switch {
	case strings.HasPrefix(origin, "http://127.0.0.1"),
		strings.HasPrefix(origin, "http://localhost"),
		strings.HasPrefix(origin, "http://[::1]"),
		strings.HasPrefix(origin, "https://127.0.0.1"),
		strings.HasPrefix(origin, "https://localhost"),
		strings.HasPrefix(origin, "https://[::1]"):
		return true
	}
	return false
}

const maxCrushAgents = 2
const maxClaudeAgents = 2

func (s *Server) AcquireAgentSlot(toolName string) (release func(), err error) {
	var counter *atomic.Int32
	var max int32
	switch toolName {
	case "crush_run":
		counter = &s.activeCrushAgents
		max = maxCrushAgents
	case "claude_run":
		counter = &s.activeClaudeAgents
		max = maxClaudeAgents
	default:
		return func() {}, nil
	}
	current := counter.Add(1)
	if current > max {
		counter.Add(-1)
		return nil, fmt.Errorf("Limite de %d agents %s atteinte", max, toolName)
	}
	return func() { counter.Add(-1) }, nil
}

func (s *Server) initStarship() {
	if _, err := exec.LookPath("starship"); err != nil {
		inst := installer.New(s.config)
		if result := inst.InstallTool("starship"); !result.Success {
			return
		}
	}
	ApplyStarshipTheme(s.config.Terminal.PromptTheme)
}

func (s *Server) buildMemoryContext(query string) string {
	store, err := s.ensureMemoryStore()
	if err != nil {
		return ""
	}
	injector := memory.NewInjector(store)
	ctx, err := injector.BuildContextBlock(query)
	if err != nil {
		return ""
	}
	return ctx
}
