package cli

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"strings"

	"github.com/andreagrandi/mcp-wire/internal/app"
	"github.com/andreagrandi/mcp-wire/internal/catalog"
	"github.com/andreagrandi/mcp-wire/internal/config"
	"github.com/andreagrandi/mcp-wire/internal/credential"
	"github.com/andreagrandi/mcp-wire/internal/service"
	targetpkg "github.com/andreagrandi/mcp-wire/internal/target"
	"github.com/andreagrandi/mcp-wire/internal/tui"
	"github.com/spf13/cobra"
	"golang.org/x/term"
)

var rootCmd = &cobra.Command{
	Use:   "mcp-wire",
	Short: "Install and configure MCP servers across AI coding tools",
	Long: `mcp-wire is a CLI tool that lets you install and configure MCP (Model Context Protocol)
servers across multiple AI coding CLI tools (Claude Code, Codex, OpenCode, etc.)
from a single interface.

Services are defined as YAML files -- no code needed to add one.
Targets are the AI tools where services get installed.`,
	Version: app.Version,
	RunE: func(cmd *cobra.Command, _ []string) error {
		return runGuidedMainMenu(cmd)
	},
}

func Execute() error {
	if !isCacheCommand(os.Args) {
		maybeStartRegistryBackgroundSync()
	}

	return rootCmd.Execute()
}

func isCacheCommand(args []string) bool {
	if len(args) < 2 {
		return false
	}

	return args[1] == "cache"
}

func canUseInteractiveUI(input io.Reader, output io.Writer) bool {
	inputFile, inputOK := input.(*os.File)
	outputFile, outputOK := output.(*os.File)
	if !inputOK || !outputOK {
		return false
	}

	return term.IsTerminal(int(inputFile.Fd())) && term.IsTerminal(int(outputFile.Fd()))
}

func runGuidedMainMenu(cmd *cobra.Command) error {
	if canUseInteractiveUI(cmd.InOrStdin(), cmd.OutOrStdout()) {
		cfg, _ := loadConfig()
		if cfg == nil {
			cfg = &config.Config{}
		}
		return tui.Run(tuiCallbacks(cfg), app.Version)
	}

	return runGuidedMainMenuPlain(cmd)
}

func runGuidedMainMenuPlain(cmd *cobra.Command) error {
	reader := bufio.NewReader(cmd.InOrStdin())
	output := cmd.OutOrStdout()

	for {
		fmt.Fprintln(output, "Main Menu")
		fmt.Fprintln(output, "  1) Install service")
		fmt.Fprintln(output, "  2) Uninstall service")
		fmt.Fprintln(output, "  3) Exit")

		choice, err := readTrimmedLine(reader, output, "Option [1-3]: ")
		if err != nil {
			return fmt.Errorf("read menu option: %w", err)
		}

		switch strings.ToLower(choice) {
		case "1", "install":
			fmt.Fprintln(output)
			if err := runInstallWizard(cmd, reader, nil, false); err != nil {
				return err
			}
			fmt.Fprintln(output)
		case "2", "uninstall":
			fmt.Fprintln(output)
			if err := runUninstallWizard(cmd, reader, nil); err != nil {
				return err
			}
			fmt.Fprintln(output)
		case "3", "exit", "q", "quit":
			fmt.Fprintln(output, "Goodbye.")
			return nil
		default:
			fmt.Fprintf(output, "Invalid option %q. Enter 1-3.\n\n", choice)
		}
	}
}

func tuiCallbacks(cfg *config.Config) tui.Callbacks {
	registryEnabled := cfg.IsFeatureEnabled("registry")
	return tui.Callbacks{
		LoadCatalog: func(source string) (*catalog.Catalog, error) {
			return loadCatalog(source, registryEnabled)
		},
		RegistrySyncStatus: func() string {
			return registrySyncStatusLine(registryEnabled)
		},
		RefreshRegistryEntry:  refreshRegistryEntry,
		CatalogEntryToService: catalogEntryToService,
		AllTargets:            allTargets,
		RegistryEnabled:       registryEnabled,

		ResolveCredential:       tuiResolveCredential,
		StoreCredential:         tuiStoreCredential,
		InstallTarget:           tuiInstallTarget,
		UninstallTarget:         tuiUninstallTarget,
		ServiceUsesOAuth:        serviceUsesOAuth,
		OAuthManualHint:         oauthManualAuthHint,
		RemoveStoredCredentials: tuiRemoveStoredCredentials,
		ListInstalledServices:   tuiListInstalledServices,
		OpenURL:                 openSetupURL,
	}
}

func tuiListInstalledServices(t targetpkg.Target, scope targetpkg.ConfigScope) ([]string, error) {
	st, ok := t.(targetpkg.ScopedTarget)
	if ok {
		return st.ListWithScope(scope)
	}
	return t.List()
}

func tuiResolveCredential(envName string) (string, string, bool) {
	envSource := newCredentialEnvSource()
	fileSource := newCredentialFileSource("")
	resolver := newCredentialResolver(envSource, fileSource)
	return resolver.Resolve(envName)
}

func tuiRemoveStoredCredentials(envNames []string) (int, error) {
	fileSource := credential.NewFileSource("")
	return removeStoredCredentials(fileSource, envNames)
}

func tuiStoreCredential(envName, value string) error {
	fileSource := credential.NewFileSource("")
	return fileSource.Store(envName, value)
}

func tuiInstallTarget(svc service.Service, env map[string]string, t targetpkg.Target, scope targetpkg.ConfigScope) error {
	scopedTarget, supportsScopes := t.(targetpkg.ScopedTarget)
	if supportsScopes && targetSupportsScope(t, scope) {
		return scopedTarget.InstallWithScope(svc, env, scope)
	}
	return t.Install(svc, env)
}

func tuiUninstallTarget(name string, t targetpkg.Target, scope targetpkg.ConfigScope) error {
	scopedTarget, supportsScopes := t.(targetpkg.ScopedTarget)
	if supportsScopes && targetSupportsScope(t, scope) {
		return scopedTarget.UninstallWithScope(name, scope)
	}
	return t.Uninstall(name)
}

func readTrimmedLine(reader *bufio.Reader, output io.Writer, prompt string) (string, error) {
	fmt.Fprint(output, prompt)
	line, err := reader.ReadString('\n')
	if err != nil {
		if len(strings.TrimSpace(line)) == 0 {
			return "", err
		}
	}

	return strings.TrimSpace(line), nil
}
