package cmd

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"

	"github.com/spf13/cobra"
	"github.com/yourorg/devour/internal/projectstate"
	"github.com/yourorg/devour/internal/scraper"
	"github.com/yourorg/devour/internal/search"
	"github.com/yourorg/devour/internal/server"
)

var serveCmd = &cobra.Command{
	Use:   "serve",
	Short: "Start the local Devour RPC server",
	Long: `Start the Devour RPC server.

Local mode (default): JSON-RPC over stdin/stdout for agent/skill integration.
Remote mode (--remote): experimental HTTP RPC endpoint at /rpc.

Examples:
  devour serve
  devour serve --remote
  devour serve --remote --port 3000`,
	RunE: runServe,
}

var (
	serveRemote bool
	servePort   int
	serveHost   string
)

func init() {
	serveCmd.Flags().BoolVar(&serveRemote, "remote", false, "run as remote HTTP server (experimental)")
	serveCmd.Flags().IntVarP(&servePort, "port", "p", 8080, "HTTP port (remote mode only)")
	serveCmd.Flags().StringVar(&serveHost, "host", "localhost", "HTTP host (remote mode only)")
}

func runServe(cmd *cobra.Command, args []string) error {
	if _, err := loadAppConfig(); err != nil {
		return fmt.Errorf("load app config for server startup: %w", err)
	}

	srvCfg := &server.Config{
		Mode:      "local",
		Transport: "stdio",
		Host:      serveHost,
		Port:      servePort,
		Handler: func(ctx context.Context, method string, params json.RawMessage) (any, error) {
			return handleServeMethod(ctx, method, params)
		},
	}

	if serveRemote {
		srvCfg.Mode = "remote"
		fmt.Printf("🚀 Starting Devour RPC server in remote experimental mode\n")
		fmt.Printf("   URL: http://%s:%d/rpc\n", serveHost, servePort)
	} else {
		fmt.Println("🚀 Starting Devour RPC server in local mode (stdio)")
		fmt.Println("   Protocol: JSON-RPC 2.0 over stdin/stdout")
	}

	srv := server.NewServer(srvCfg)
	if err := srv.Start(context.Background()); err != nil {
		return fmt.Errorf("start rpc server: %w", err)
	}
	return nil
}

func handleServeMethod(ctx context.Context, method string, params json.RawMessage) (any, error) {
	// The method implementation needs full typed config. Load per-call to avoid stale state.
	loadedCfg, err := loadAppConfig()
	if err != nil {
		return nil, fmt.Errorf("load app config for rpc method %q: %w", method, err)
	}

	switch strings.TrimSpace(method) {
	case "devour_query":
		var req struct {
			Query     string  `json:"query"`
			Limit     int     `json:"limit"`
			Threshold float64 `json:"threshold"`
		}
		if len(params) > 0 {
			if err := json.Unmarshal(params, &req); err != nil {
				return nil, fmt.Errorf("decode devour_query params: %w", err)
			}
		}
		engine := search.NewEngine(loadedCfg)
		results, stats, err := engine.Search(ctx, req.Query, search.SearchOptions{Limit: req.Limit, Threshold: req.Threshold})
		if err != nil {
			return nil, fmt.Errorf("run devour_query search: %w", err)
		}
		return map[string]any{"query": req.Query, "count": len(results), "results": results, "indexed": stats.Documents}, nil

	case "devour_status":
		docsStats, err := projectstate.CollectDocsStats(loadedCfg.Storage.DocsDir)
		if err != nil {
			return nil, fmt.Errorf("collect docs stats: %w", err)
		}
		state, err := projectstate.LoadSourceState(loadedCfg.Storage.MetadataDir)
		if err != nil {
			return nil, fmt.Errorf("load source state: %w", err)
		}
		engine := search.NewEngine(loadedCfg)
		idxStats, err := engine.EnsureIndexed(ctx)
		if err != nil {
			return nil, fmt.Errorf("ensure search index: %w", err)
		}
		return map[string]any{
			"documents":       docsStats.DocumentCount,
			"storage_bytes":   docsStats.StorageBytes,
			"last_updated":    docsStats.LastUpdated,
			"sources":         state.Sources,
			"indexed_docs":    idxStats.Documents,
			"index_timestamp": idxStats.LastIndexedAt,
		}, nil

	case "devour_scrape":
		var req struct {
			Source      string   `json:"source"`
			Type        string   `json:"type"`
			Format      string   `json:"format"`
			Output      string   `json:"output"`
			Query       string   `json:"query"`
			ResultLimit int      `json:"result_limit"`
			Domains     []string `json:"domains"`
			Include     []string `json:"include"`
			Exclude     []string `json:"exclude"`
		}
		if err := json.Unmarshal(params, &req); err != nil {
			return nil, fmt.Errorf("decode devour_scrape params: %w", err)
		}
		if strings.TrimSpace(req.Source) == "" {
			return nil, fmt.Errorf("source is required")
		}
		st := scraper.SourceType(req.Type)
		if st == "" {
			st = detectSourceType(req.Source)
		}
		source := &scraper.Source{
			Name:        extractName(req.Source),
			Type:        st,
			URL:         req.Source,
			Query:       strings.TrimSpace(req.Query),
			ResultLimit: req.ResultLimit,
			Domains:     append([]string(nil), req.Domains...),
			Include:     append([]string(nil), req.Include...),
			Exclude:     append([]string(nil), req.Exclude...),
		}
		if st == scraper.SourceTypeLocal {
			source.Path = req.Source
		}
		applySourceProfile(source)
		prevFormat := scrapeFormat
		prevOutput := scrapeOutput
		prevAllowEmpty := scrapeAllowEmpty
		defer func() {
			scrapeFormat = prevFormat
			scrapeOutput = prevOutput
			scrapeAllowEmpty = prevAllowEmpty
		}()
		scrapeFormat = coalesceString(req.Format, "json")
		scrapeOutput = req.Output
		scrapeAllowEmpty = false
		count, err := scrapeOne(nil, loadedCfg, source, resolveOutputDir(loadedCfg, req.Output))
		if err != nil {
			return nil, fmt.Errorf("run scrape for %q: %w", req.Source, err)
		}
		return map[string]any{"source": req.Source, "type": st, "documents": count}, nil

	case "devour_ask":
		var req struct {
			Question string `json:"question"`
			Limit    int    `json:"limit"`
		}
		if err := json.Unmarshal(params, &req); err != nil {
			return nil, fmt.Errorf("decode devour_ask params: %w", err)
		}
		if strings.TrimSpace(req.Question) == "" {
			return nil, fmt.Errorf("question is required")
		}
		limit := req.Limit
		if limit <= 0 {
			limit = 5
		}
		engine := search.NewEngine(loadedCfg)
		results, _, err := engine.Search(ctx, req.Question, search.SearchOptions{Limit: limit})
		if err != nil {
			return nil, fmt.Errorf("run devour_ask search: %w", err)
		}
		summary := "No relevant docs found."
		if len(results) > 0 {
			summary = results[0].Snippet
		}
		return map[string]any{"question": req.Question, "summary": summary, "sources": results}, nil

	case "devour_sync":
		prevForce, prevRebuild, prevSource := syncForce, syncRebuild, syncSource
		var req struct {
			Source  string `json:"source"`
			Force   bool   `json:"force"`
			Rebuild bool   `json:"rebuild"`
		}
		if len(params) > 0 {
			if err := json.Unmarshal(params, &req); err != nil {
				return nil, fmt.Errorf("decode devour_sync params: %w", err)
			}
		}
		syncForce = req.Force
		syncRebuild = req.Rebuild
		syncSource = req.Source
		defer func() {
			syncForce, syncRebuild, syncSource = prevForce, prevRebuild, prevSource
		}()
		err := runSync(nil, nil)
		if err != nil {
			return nil, fmt.Errorf("run devour_sync: %w", err)
		}
		return map[string]any{"ok": true}, nil

	default:
		return nil, fmt.Errorf("unknown method: %s", method)
	}
}

func coalesceString(primary, fallback string) string {
	if strings.TrimSpace(primary) != "" {
		return primary
	}
	return fallback
}
