package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"

	"gitgud.io/mike/mpv-manager/internal/assets"
	_ "gitgud.io/mike/mpv-manager/internal/webassets"
	"gitgud.io/mike/mpv-manager/pkg/config"
	"gitgud.io/mike/mpv-manager/pkg/constants"
	"gitgud.io/mike/mpv-manager/pkg/installer"
	"gitgud.io/mike/mpv-manager/pkg/log"
	"gitgud.io/mike/mpv-manager/pkg/platform"
	"gitgud.io/mike/mpv-manager/pkg/tui"
	"gitgud.io/mike/mpv-manager/pkg/version"
	"gitgud.io/mike/mpv-manager/pkg/web"
	tea "github.com/charmbracelet/bubbletea"
)

var (
	runMode       string
	webPort       string
	webAddr       string
	webAutoOpen   bool
	showHelp      bool
	showVersion   bool
	checkUpdate   bool
	forceUpdate   bool
	installPath   string
	installMethod string
	verboseMode   bool
	debugMode     bool
)

// webOpenExplicitlySet tracks if --web-open was explicitly passed by the user
var webOpenExplicitlySet bool

func init() {
	flag.StringVar(&runMode, "mode", "web", "Run mode: web (default), tui, or cli")
	flag.StringVar(&runMode, "m", "web", "Run mode: web (default), tui, or cli (shorthand)")
	flag.StringVar(&webPort, "web-port", "6787", "Port for web UI (web mode only)")
	flag.StringVar(&webAddr, "web-address", "127.0.0.1", "Address for web UI (web mode only)")
	flag.BoolVar(&webAutoOpen, "web-open", true, "Auto-open browser on launch (web mode only)")

	// Handle simple subcommand syntax: ./mpv-manager [tui|web|cli] [options]
	// Check if first argument is a mode subcommand (before flag parsing)
	if len(os.Args) > 1 {
		firstArg := os.Args[1]
		if firstArg == "tui" || firstArg == "web" || firstArg == "cli" {
			runMode = firstArg
			// Remove the subcommand from args so flag.Parse() works correctly
			os.Args = append(os.Args[:1], os.Args[2:]...)
		}
	}

	// Check if --web-open was explicitly passed by scanning os.Args
	for _, arg := range os.Args[1:] {
		if arg == "--web-open" || arg == "--web-open=true" || arg == "--web-open=false" {
			webOpenExplicitlySet = true
			break
		}
	}
	flag.BoolVar(&showHelp, "help", false, "Show help message")
	flag.BoolVar(&showHelp, "h", false, "Show help message (shorthand)")
	flag.BoolVar(&showVersion, "version", false, "Show version information")
	flag.BoolVar(&showVersion, "v", false, "Show version information (shorthand)")
	flag.BoolVar(&checkUpdate, "check-update", false, "Check for installer updates (tui mode only)")
	flag.BoolVar(&forceUpdate, "update", false, "Update installer (tui mode only)")
	flag.StringVar(&installPath, "path", "", "Custom installation path")
	flag.StringVar(&installMethod, "method", "", "Installation method (cli mode only)")
	flag.BoolVar(&verboseMode, "verbose", false, "Enable verbose output")
	flag.BoolVar(&verboseMode, "V", false, "Enable verbose output (shorthand)")
	flag.BoolVar(&debugMode, "debug", false, "Enable debug output (implies verbose)")
	flag.Parse()
}

func main() {
	log.Init()
	defer log.Close()

	if verboseMode || debugMode {
		log.SetVerboseMode(true)
	}
	if debugMode {
		log.SetDebugMode(true)
	}

	if showHelp {
		printHelp()
		os.Exit(0)
	}

	if showVersion {
		printVersion()
		os.Exit(0)
	}

	runMode = strings.ToLower(runMode)

	if runMode != "tui" && runMode != "web" && runMode != "cli" {
		fmt.Fprintf(os.Stderr, "Error: Invalid mode '%s'. Must be 'tui', 'web', or 'cli'.\n\n", runMode)
		printHelp()
		os.Exit(1)
	}

	switch runMode {
	case "tui":
		runTUIMode()
	case "cli":
		runCLIMode()
	default:
		runWebMode()
	}
}

func runTUIMode() {
	if webOpenExplicitlySet && webAutoOpen {
		fmt.Fprintln(os.Stderr, "Warning: --web-open flag only applies to web mode. Ignoring.")
	}

	plat := platform.Detect()
	releaseInfo, _ := fetchReleaseInfo()
	inst := installer.NewInstaller(*releaseInfo, config.GetInstallPath())

	var methods []installer.InstallMethod
	handler := installer.NewInstallationHandler(plat, inst)
	if handler != nil && handler.PlatformInstaller != nil {
		methods = handler.PlatformInstaller.GetAvailableMethods(false)
	}

	installedApps := config.GetInstalledApps()

	menuItems := tui.NewMainMenuItems(len(installedApps))
	model := tui.NewModel()
	model.SetMenuItems(menuItems, "Main Menu")
	model.SetInstallerWithMethods(inst, plat, methods)

	var gpuString string
	if plat.GPUInfo != nil && len(plat.GPUInfo.Models) > 0 {
		gpuString = strings.Join(plat.GPUInfo.Models, ", ")
	} else {
		gpuString = "Unknown GPU"
	}

	var hwaString string
	if plat.GPUInfo != nil && len(plat.GPUInfo.SupportedCodecs) > 0 {
		hwaString = strings.Join(plat.GPUInfo.SupportedCodecs, ",")
	} else {
		hwaString = "Unknown"
	}

	platformInfo := tui.PlatformInfo{
		AppVersion:   version.CurrentVersion,
		OSType:       string(plat.OSType),
		Arch:         plat.Arch,
		Distro:       plat.Distro,
		DistroFamily: plat.DistroFamily,
		CPU:          plat.CPUInfo.Model,
		CPUVendor:    plat.CPUInfo.Vendor,
		GPU:          gpuString,
		GPUVendor:    plat.GPUInfo.Brand,
		HWA:          hwaString,
	}
	model.SetPlatform(platformInfo)

	if checkUpdate {
		checkForUpdates()
		os.Exit(0)
	}

	if forceUpdate {
		selfUpdate()
		os.Exit(0)
	}

	if installMethod != "" {
		runCLIMode()
		os.Exit(0)
	}

	ensureInstallerShortcut()
	if runtime.GOOS == "windows" {
		autoResizeConsole()
	}

	p := tea.NewProgram(model, tea.WithAltScreen())
	if _, err := p.Run(); err != nil {
		fmt.Printf("Error: %v\n", err)
		os.Exit(1)
	}
}

func runWebMode() {
	if installPath != "" {
		fmt.Fprintln(os.Stderr, "Warning: --path flag not supported in web mode. Ignoring.")
	}
	if installMethod != "" {
		fmt.Fprintln(os.Stderr, "Warning: --method flag not supported in web mode. Ignoring.")
	}
	if checkUpdate {
		fmt.Fprintln(os.Stderr, "Warning: --check-update flag not supported in web mode. Ignoring.")
	}
	if forceUpdate {
		fmt.Fprintln(os.Stderr, "Warning: --update flag not supported in web mode. Ignoring.")
	}

	// Run platform detection and release info fetching concurrently
	var plat *platform.Platform
	var releaseInfo *installer.ReleaseInfo
	var wg sync.WaitGroup
	wg.Add(2)

	go func() {
		defer wg.Done()
		plat = platform.Detect()
	}()

	go func() {
		defer wg.Done()
		var err error
		releaseInfo, err = fetchReleaseInfo()
		if err != nil {
			log.Warn("Failed to fetch release info during startup: " + err.Error() + " - Using empty release info as fallback")
			// Create empty release info as fallback
			releaseInfo = &installer.ReleaseInfo{}
		}
	}()

	wg.Wait()

	// These must happen after platform detection
	cfg, _ := config.Load()
	inst := installer.NewInstaller(*releaseInfo, config.GetInstallPath())

	server := web.NewServer(plat, cfg, inst, releaseInfo)

	// Create Web UI shortcut on startup
	log.Info("Creating Web UI shortcut...")
	handler := installer.NewInstallationHandler(plat, inst)
	outputChan := make(chan string, 100)
	cr := installer.NewCommandRunner(outputChan, nil)
	if err := handler.CreateWebUIShortcutWithOutput(cr); err != nil {
		log.Warn("Failed to create Web UI shortcut: " + err.Error())
	} else {
		log.Info("Web UI shortcut created successfully")
	}

	var addr string
	if webAddr != "127.0.0.1" {
		addr = webAddr
	} else {
		addr = fmt.Sprintf("%s:%s", webAddr, webPort)
	}

	go func() {
		if err := server.ListenAndServe(addr); err != nil {
			log.Error("Web server error: " + err.Error())
		}
	}()

	// Only launch browser if webAutoOpen is true
	browserLaunched := false
	if webAutoOpen {
		url := fmt.Sprintf("http://%s", addr)
		if err := openBrowser(url); err != nil {
			log.Error("Failed to open browser: " + err.Error())
		} else {
			browserLaunched = true
			log.Info("Browser opened automatically: " + url)
		}
	} else {
		log.Info("Browser auto-open is disabled (--web-open=false)")
	}

	printStartupBanner(addr, browserLaunched)

	select {}
}

func printStartupBanner(addr string, browserOpened bool) {
	border := strings.Repeat("#", 39)
	url := fmt.Sprintf("http://%s", addr)

	fmt.Println(border)
	fmt.Printf("MPV.Rocks - MPV Manager | https://mpv.rocks\n")
	fmt.Printf("version: %s\n\n", version.CurrentVersion)

	if browserOpened {
		fmt.Println("Browser Launched")
	}
	fmt.Printf("Access Web App: %s\n\n", url)

	fmt.Println("Add -h for Help and options | CTRL+C to close app")
	fmt.Println(border)
}

func printHelp() {
	fmt.Println("MPV.Rocks Manager - MPV Media Player Management")
	fmt.Println("Version:", version.CurrentVersion)
	fmt.Println()
	fmt.Println("USAGE:")
	fmt.Println("  mpv-manager [COMMAND] [OPTIONS]")
	fmt.Println()
	fmt.Println("COMMANDS:")
	fmt.Println("  web      Run in Web UI mode (default)")
	fmt.Println("  tui      Run in TUI mode (terminal interface)")
	fmt.Println("  cli      Run in CLI mode (requires --method)")
	fmt.Println()
	fmt.Println("OPTIONS:")
	fmt.Println("  -m, --mode web|tui|cli    Alternative way to set mode")
	fmt.Println("  -h, --help                Show help message")
	fmt.Println("  -v, --version             Show version information")
	fmt.Println("  -V, --verbose             Enable verbose output")
	fmt.Println("      --debug               Enable debug output (implies verbose)")
	fmt.Println()
	fmt.Println("TUI MODE OPTIONS:")
	fmt.Println("      --check-update        Check for installer updates")
	fmt.Println("      --update              Update installer")
	fmt.Println("      --path PATH           Custom installation path")
	fmt.Println("      --method METHOD       Installation method (runs CLI mode)")
	fmt.Println()
	fmt.Println("WEB MODE OPTIONS:")
	fmt.Println("      --web-port PORT       Port for web UI (default: 6787)")
	fmt.Println("      --web-address ADDR    Address for web UI (default: 127.0.0.1)")
	fmt.Println("      --web-open            Auto-open browser on launch (default: true)")
	fmt.Println("      --web-open=false      Do not auto-open browser")
	fmt.Println()
	fmt.Println("CLI MODE OPTIONS:")
	fmt.Println("      --method METHOD       Installation method (required)")
	fmt.Println("      --path PATH           Custom installation path")
	fmt.Println()
	fmt.Println("EXAMPLES:")
	fmt.Println("  mpv-manager                          Run in Web UI mode (default)")
	fmt.Println("  mpv-manager web                      Run in Web UI mode")
	fmt.Println("  mpv-manager tui                      Run in TUI mode")
	fmt.Println("  mpv-manager cli --method mpv-binary  Run CLI install")
	fmt.Println("  mpv-manager --web-port 8080          Run Web UI on port 8080")
	fmt.Println("  mpv-manager --web-open=false         Run Web UI without opening browser")
	fmt.Println("  mpv-manager --web-address 0.0.0.0    Bind to all interfaces")
	fmt.Println()
	fmt.Println("For more information, visit: https://mpv.rocks")
}

func printVersion() {
	fmt.Println("MPV.Rocks Installer")
	fmt.Println("Version:", version.CurrentVersion)
	fmt.Println("Build Time:", version.BuildTime)
	fmt.Println("Git Commit:", version.GitCommit)
}

func openBrowser(url string) error {
	var cmd *exec.Cmd
	switch runtime.GOOS {
	case "windows":
		cmd = exec.Command("rundll32", "url.dll,FileProtocolHandler", url)
	case "darwin":
		cmd = exec.Command("open", url)
	default:
		cmd = exec.Command("xdg-open", url)
	}
	return cmd.Start()
}

func fetchReleaseInfo() (*installer.ReleaseInfo, error) {
	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Get(version.ReleasesURL)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var info installer.ReleaseInfo
	if err := json.Unmarshal(body, &info); err != nil {
		return nil, err
	}

	return &info, nil
}

func checkForUpdates() {
	fmt.Println("Checking for updates...")
	result := version.CheckForUpdate()
	fmt.Printf("Current version: %s\n", result.CurrentVersion)
	fmt.Printf("Latest version: %s\n", result.LatestVersion)

	if result.UpdateAvailable {
		fmt.Println("An update is available!")
	} else {
		fmt.Println("You are running the latest version.")
	}
}

func selfUpdate() {
	fmt.Println("Updating installer...")
}

func runCLIMode() {
	// CLI mode requires --method to be specified
	if installMethod == "" {
		fmt.Fprintln(os.Stderr, "Error: CLI mode requires --method to be specified.")
		fmt.Fprintln(os.Stderr, "Usage: mpv-manager cli --method <method> [--path <path>]")
		fmt.Fprintln(os.Stderr, "")
		fmt.Fprintln(os.Stderr, "Available methods can be found in the Web UI or TUI.")
		os.Exit(1)
	}

	plat := platform.Detect()

	if installPath != "" {
		config.SetInstallPath(installPath)
	}

	fmt.Printf("Platform: %s\n", plat.String())
	fmt.Printf("Install Path: %s\n", config.GetInstallPath())
	fmt.Printf("Method: %s\n", installMethod)

	releaseInfo, err := fetchReleaseInfo()
	if err != nil {
		errMsg := fmt.Sprintf("Error fetching release info: %v", err)
		log.Error(errMsg)
		fmt.Println(errMsg)
		os.Exit(1)
	}

	_ = installer.NewInstaller(*releaseInfo, config.GetInstallPath())

	fmt.Println("Installing MPV...")
	time.Sleep(2 * time.Second)
	fmt.Println("Installation complete!")
}

func autoResizeConsole() {
	cmd := exec.Command("powershell", "-Command", "& {$host.UI.RawUI.WindowSize = New-Object System.Management.Automation.Host.RawUi.WindowSize; $host.UI.RawUI.WindowSize.Width = 120; $host.UI.RawUI.WindowSize.Height = 40}")
	if err := cmd.Run(); err != nil {
		log.Debug("Failed to auto-resize console: " + err.Error())
	}
}

func ensureInstallerShortcut() {
	if runtime.GOOS != "windows" {
		return
	}

	installDir := config.GetInstallPath()
	installerDir := filepath.Join(installDir, constants.InstallerDir)
	installerBinaryPath := filepath.Join(installerDir, "mpv-manager.exe")

	// Create directory if it doesn't exist
	if err := os.MkdirAll(installerDir, 0755); err != nil {
		log.Error("Failed to create installer dir: " + err.Error())
		return
	}

	execPath, err := os.Executable()
	if err != nil {
		log.Error("Failed to get executable path: " + err.Error())
		return
	}

	// Check if we need to copy/update the binary
	shouldCopy := false
	if _, err := os.Stat(installerBinaryPath); os.IsNotExist(err) {
		shouldCopy = true
		log.Info("Copying MPV Manager binary to user directory...")
	} else {
		// Check if the binary is different (e.g., newer version)
		currentExe, err1 := os.ReadFile(execPath)
		existingBinary, err2 := os.ReadFile(installerBinaryPath)
		if err1 == nil && err2 == nil && !bytes.Equal(currentExe, existingBinary) {
			shouldCopy = true
			log.Info("Updating MPV Manager binary...")
		}
	}

	if shouldCopy {
		input, err := os.ReadFile(execPath)
		if err != nil {
			log.Error("Failed to read executable: " + err.Error())
			return
		}
		if err := os.WriteFile(installerBinaryPath, input, 0755); err != nil {
			log.Error("Failed to write binary: " + err.Error())
		} else {
			log.Info("MPV Manager binary copied to: " + installerBinaryPath)
		}
	}

	// Extract uninstall script
	uninstallBat := filepath.Join(installerDir, constants.UninstallBatFileName)
	if _, err := os.Stat(uninstallBat); os.IsNotExist(err) {
		if err := assets.ExtractUninstallBat(installerDir); err != nil {
			log.Warn("Failed to extract uninstall script: " + err.Error())
		} else {
			log.Info("Uninstall script extracted")
		}
	}
}
