/*
 * ChatCLI - Command Line Interface for LLM interaction
 * Copyright (c) 2024 Edilson Freitas
 * License: Apache-2.0
 */
package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/diillson/chatcli/cli"
	"github.com/diillson/chatcli/cmd"
	"github.com/diillson/chatcli/config"
	"github.com/diillson/chatcli/i18n"
	"github.com/diillson/chatcli/llm/manager"
	"github.com/diillson/chatcli/ui/theme"
	"github.com/diillson/chatcli/utils"
	"github.com/diillson/chatcli/version"
	"github.com/joho/godotenv"
	"go.uber.org/zap"
)

// isSubcommand reports whether arg is a recognized top-level subcommand and
// dispatches it. It returns true when a subcommand was handled.
func dispatchSubcommand() bool {
	if len(os.Args) <= 1 {
		return false
	}
	subcmd := os.Args[1]
	switch subcmd {
	case "server", "serve", "connect", "watch", "mcp-server", "mcp-serve", "acp", "gateway":
		runSubcommand(subcmd, os.Args[2:])
		return true
	case "daemon":
		runDaemonSubcommand(os.Args[2:])
		return true
	}
	return false
}

// printVersionInfo prints version details (including update check) and is used
// for the -version flag.
func printVersionInfo() {
	versionInfo := version.GetCurrentVersion()
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	latest, hasUpdate, err := version.CheckLatestVersionWithContext(ctx)
	fmt.Println(version.FormatVersionInfo(versionInfo, latest, hasUpdate, err))
}

// applyStackSpotFlags applies StackSpot realm/agent overrides when the target
// provider is StackSpot.
func applyStackSpotFlags(llmManager manager.LLMManager, targetProvider string, opts *cli.Options, logger *zap.Logger) {
	if strings.ToUpper(targetProvider) != "STACKSPOT" {
		return
	}
	if opts.Realm != "" {
		llmManager.SetStackSpotRealm(opts.Realm)
		logger.Info("Realm/Tenant do StackSpot sobrescrito via flag", zap.String("realm", opts.Realm))
	}
	if opts.AgentID != "" {
		llmManager.SetStackSpotAgentID(opts.AgentID)
		logger.Info("Agent ID do StackSpot sobrescrito via flag", zap.String("agent-id", opts.AgentID))
	}
}

// installSignalHandlers wires process signals to the correct cancellation
// scope. It is the SINGLE handler for SIGINT and SIGTERM — having two
// independent handlers is exactly what caused the coder/agent re-entry bug
// (see below).
//
//   - SIGTERM is always a shutdown request → cancel the root context.
//   - SIGINT (Ctrl+C) cancels the in-flight operation when one is running
//     (the interactive "interrupt this, keep the session" semantics), and only
//     shuts the session down when idle at the prompt.
//
// CRITICAL: a SIGINT delivered WHILE a coder/agent operation runs must NOT
// cancel the root context. During a security confirmation the TTY is in cooked
// mode, so Ctrl+C arrives as a real signal (not a go-prompt key). If that
// cancels the root context, every later coder/agent run — which derives its
// context from root — is born already-cancelled and "doesn't fire", while chat
// keeps working on a fresh background context. The only recovery was restarting
// the process. Cancelling just the operation is the correct response; the root
// context stays alive for the rest of the session.
//
// On a clean shutdown the root cancel lets chatCLI.Start() return so the
// deferred cli.cleanup() runs (stopping MCP child processes, draining the
// scheduler, flushing history) — os.Exit() would skip every defer and orphan
// the npx-spawned MCP servers.
func installSignalHandlers(isExecuting func() bool, cancelOperation, cancelRoot func(), logger *zap.Logger) {
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

	go func() {
		for sig := range sigChan {
			if sig == syscall.SIGINT && isExecuting() {
				logger.Info("Cancelando operação em andamento (SIGINT)")
				cancelOperation()
				continue
			}
			logger.Info("Encerrando aplicação", zap.String("signal", sig.String()))
			cancelRoot()
		}
	}()
}

func main() {
	// Check for subcommands (server, connect) before processing standard flags.
	// These subcommands have their own flag sets and should not go through cli.Parse().
	if dispatchSubcommand() {
		return
	}

	args := cli.PreprocessArgs(os.Args[1:])
	opts, err := cli.Parse(args)
	if err != nil {
		fmt.Println(err)
		os.Exit(2)
	}

	i18n.Init()

	if opts.Version {
		printVersionInfo()
		return
	}

	envFilePath := os.Getenv("CHATCLI_DOTENV")
	if envFilePath == "" {
		envFilePath = ".env"
	} else {
		expanded, err := utils.ExpandPath(envFilePath)
		if err == nil {
			envFilePath = expanded
		} else {
			fmt.Println(i18n.T("main.warn_expand_path", envFilePath, err))
		}
	}

	if err := godotenv.Load(envFilePath); err != nil && !os.IsNotExist(err) {
		// CORREÇÃO: Usar Println com i18n.T
		fmt.Println(i18n.T("main.error_dotenv_not_found", envFilePath))
	}

	// Resolve the UI theme now that .env is loaded (CHATCLI_THEME may live
	// only in the dotenv file, after the theme package's own init ran).
	theme.InitFromEnv()

	logger, err := utils.InitializeLogger()
	if err != nil {
		// CORREÇÃO: Usar Println com i18n.T
		fmt.Println(i18n.T("main.error_logger_init", err))
		os.Exit(1)
	}

	// Silence Go's default logger to prevent http2/net internal messages
	// (e.g. "RoundTrip retrying after failure") from leaking to stderr.
	log.SetOutput(io.Discard)

	config.InitGlobal(logger)
	config.Global.Load()
	utils.ApplyGlobalTLSTrust(logger)
	utils.LogStartupInfo(logger)

	defer func() {
		if err := logger.Sync(); err != nil {
			// On Windows, syncing stdout/stderr returns "invalid handle" error.
			// This is a known zap issue; ignore it safely.
			msg := err.Error()
			if !strings.Contains(msg, "/dev/stdout") &&
				!strings.Contains(msg, "/dev/stderr") &&
				!strings.Contains(msg, "invalid argument") &&
				!strings.Contains(msg, "inappropriate ioctl") {
				fmt.Fprintf(os.Stderr, "Erro ao fechar logger: %v\n", err)
			}
		}
	}()

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	llmManager, err := manager.NewLLMManager(logger)
	if err != nil {
		logger.Fatal("Erro ao inicializar o LLMManager", zap.Error(err))
	}

	availableProviders := llmManager.GetAvailableProviders()
	if len(availableProviders) == 0 && (opts.PromptFlagUsed || cli.HasStdin()) {
		logger.Warn("Nenhum provedor LLM configurado via .env, dependendo de flags para funcionar.")
	} else if len(availableProviders) == 0 {
		fmt.Println(i18n.T("main.error_no_provider"))
		fmt.Println("Tip: use /auth login anthropic | openai-codex to authenticate via OAuth.")
	}

	chatCLI, err := cli.NewChatCLI(ctx, llmManager, logger)
	if err != nil {
		logger.Fatal("Erro ao inicializar o ChatCLI", zap.Error(err))
	}

	chatCLI.UserMaxTokens = opts.MaxTokens

	targetProvider := opts.Provider
	if targetProvider == "" {
		targetProvider = chatCLI.Provider
	}

	applyStackSpotFlags(llmManager, targetProvider, opts, logger)

	if err := chatCLI.ApplyOverrides(ctx, llmManager, opts.Provider, opts.Model); err != nil {
		// CORREÇÃO: Usar Fprintln com i18n.T
		fmt.Fprintln(os.Stderr, i18n.T("main.error_apply_overrides", err))
		logger.Error("Erro fatal ao aplicar overrides de provider/model via flags", zap.Error(err))
		os.Exit(1)
	}

	installSignalHandlers(chatCLI.IsExecuting, chatCLI.CancelOperation, cancel, logger)

	if chatCLI.HandleOneShotOrFatal(ctx, opts) {
		return
	}

	chatCLI.Start(ctx)
}

// runSubcommand handles the 'server' and 'connect' subcommands.
// These have their own initialization flow separate from the standard CLI.
func runSubcommand(subcmd string, args []string) {
	i18n.Init()

	// Load .env
	envFilePath := os.Getenv("CHATCLI_DOTENV")
	if envFilePath == "" {
		envFilePath = ".env"
	} else {
		expanded, err := utils.ExpandPath(envFilePath)
		if err == nil {
			envFilePath = expanded
		}
	}
	_ = godotenv.Load(envFilePath)
	theme.InitFromEnv()

	logger, err := utils.InitializeLogger()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to initialize logger: %v\n", err)
		os.Exit(1)
	}

	config.InitGlobal(logger)
	config.Global.Load()
	utils.ApplyGlobalTLSTrust(logger)

	defer func() {
		if err := logger.Sync(); err != nil {
			msg := err.Error()
			if !strings.Contains(msg, "/dev/stdout") &&
				!strings.Contains(msg, "/dev/stderr") &&
				!strings.Contains(msg, "invalid argument") &&
				!strings.Contains(msg, "inappropriate ioctl") {
				fmt.Fprintf(os.Stderr, "Error closing logger: %v\n", err)
			}
		}
	}()

	llmMgr, err := manager.NewLLMManager(logger)
	if err != nil {
		logger.Fatal("Failed to initialize LLMManager", zap.Error(err))
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	switch subcmd {
	case "server", "serve":
		if err := cmd.RunServer(args, llmMgr, logger); err != nil {
			logger.Fatal("Server failed", zap.Error(err))
		}
	case "connect":
		if err := cmd.RunConnect(ctx, args, llmMgr, logger); err != nil {
			fmt.Fprintf(os.Stderr, "Error: %v\n", err)
			os.Exit(1)
		}
	case "watch":
		if err := cmd.RunWatch(ctx, args, llmMgr, logger); err != nil {
			fmt.Fprintf(os.Stderr, "Error: %v\n", err)
			os.Exit(1)
		}
	case "mcp-server", "mcp-serve": // mcp-serve kept as a back-compat alias
		if err := cmd.RunMCPServe(args, llmMgr, logger); err != nil {
			logger.Fatal("MCP server failed", zap.Error(err))
		}
	case "acp":
		if err := cmd.RunACP(args, llmMgr, logger); err != nil {
			logger.Fatal("ACP server failed", zap.Error(err))
		}
	case "gateway":
		if err := cmd.RunGateway(args, llmMgr, logger); err != nil {
			logger.Fatal("Gateway failed", zap.Error(err))
		}
	}
}

// runDaemonSubcommand handles the "daemon" subcommand. Standalone
// because the scheduler daemon does not need an LLMManager (it doesn't
// run agent tasks until a CLI attaches and delegates a bridge).
func runDaemonSubcommand(args []string) {
	i18n.Init()

	envFilePath := os.Getenv("CHATCLI_DOTENV")
	if envFilePath == "" {
		envFilePath = ".env"
	} else if expanded, err := utils.ExpandPath(envFilePath); err == nil {
		envFilePath = expanded
	}
	_ = godotenv.Load(envFilePath)
	theme.InitFromEnv()

	logger, err := utils.InitializeLogger()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to initialize logger: %v\n", err)
		os.Exit(1)
	}
	defer func() {
		if err := logger.Sync(); err != nil {
			msg := err.Error()
			if !strings.Contains(msg, "/dev/stdout") &&
				!strings.Contains(msg, "/dev/stderr") &&
				!strings.Contains(msg, "invalid argument") &&
				!strings.Contains(msg, "inappropriate ioctl") {
				fmt.Fprintf(os.Stderr, "Error closing logger: %v\n", err)
			}
		}
	}()

	config.InitGlobal(logger)
	config.Global.Load()
	utils.ApplyGlobalTLSTrust(logger)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	if err := cmd.RunDaemon(ctx, args, logger); err != nil {
		fmt.Fprintf(os.Stderr, "Error: %v\n", err)
		os.Exit(1)
	}
}
