package deploy

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"time"

	"github.com/nats-io/nats.go"
)

// GameServerStarter abstracts the game server process manager so the deployer
// can set the executable path and start the server without depending on the
// concrete process.GameServer type. The existing GameServer will implement
// UpdatePath in a separate task.
type GameServerStarter interface {
	Start() error
	UpdatePath(path string)
}

// Deployer orchestrates one-click Rust server deployment. It downloads SteamCMD,
// installs the Rust Dedicated Server, generates server.cfg, and starts the server
// process — publishing progress updates to NATS at each stage so the frontend can
// display real-time deployment status.
type Deployer struct {
	nc         *nats.Conn
	licenseID  string
	installDir string
	gameServer GameServerStarter
}

// NewDeployer creates a new Deployer instance.
func NewDeployer(nc *nats.Conn, licenseID, installDir string, gs GameServerStarter) *Deployer {
	return &Deployer{
		nc:         nc,
		licenseID:  licenseID,
		installDir: installDir,
		gameServer: gs,
	}
}

// Deploy executes the full deployment pipeline: SteamCMD install, Rust server
// download, config generation, and server startup. If any stage fails, a "failed"
// status is published and the error is returned. Progress updates are published
// to NATS at each stage transition.
func (d *Deployer) Deploy(cfg DeployConfig) error {
	// Stage 1: SteamCMD
	log.Printf("Deploy: starting SteamCMD installation for license %s", d.licenseID)
	d.publishStatus("downloading_steamcmd", 0, "Checking for existing SteamCMD installation...")

	steamcmdPath, err := InstallSteamCMD(d.installDir)
	if err != nil {
		d.publishStatus("failed", 0, "SteamCMD installation failed", err.Error())
		return fmt.Errorf("steamcmd install failed: %w", err)
	}

	log.Printf("Deploy: SteamCMD ready at %s", steamcmdPath)
	d.publishStatus("downloading_steamcmd", 100, "SteamCMD ready")

	// Stage 2: Download Rust Dedicated Server
	log.Printf("Deploy: downloading Rust Dedicated Server via SteamCMD")
	d.publishStatus("downloading_rust", 0, "Downloading Rust Dedicated Server via SteamCMD...")

	if err := DownloadRustServer(steamcmdPath, d.installDir); err != nil {
		d.publishStatus("failed", 0, "Rust server download failed", err.Error())
		return fmt.Errorf("rust server download failed: %w", err)
	}

	log.Printf("Deploy: Rust Dedicated Server installed")
	d.publishStatus("downloading_rust", 100, "Rust Dedicated Server installed")

	// Stage 3: Generate server.cfg
	log.Printf("Deploy: generating server.cfg")
	d.publishStatus("configuring", 0, "Generating server.cfg...")

	if err := GenerateServerCfg(d.installDir, cfg); err != nil {
		d.publishStatus("failed", 0, "Server configuration failed", err.Error())
		return fmt.Errorf("config generation failed: %w", err)
	}

	log.Printf("Deploy: server.cfg written")
	d.publishStatus("configuring", 100, "Server configured")

	// Stage 4: Start the server
	log.Printf("Deploy: starting Rust server")
	d.publishStatus("starting", 0, "Starting Rust server...")

	var exePath string
	switch runtime.GOOS {
	case "windows":
		exePath = filepath.Join(d.installDir, "server", "RustDedicated.exe")
	default:
		exePath = filepath.Join(d.installDir, "server", "RustDedicated")
	}

	d.gameServer.UpdatePath(exePath)

	if err := d.gameServer.Start(); err != nil {
		d.publishStatus("failed", 0, "Server failed to start", err.Error())
		return fmt.Errorf("server start failed: %w", err)
	}

	log.Printf("Deploy: Rust server is now running")
	d.publishStatus("online", 100, "Rust server is now running")

	return nil
}

// DownloadRustServer runs SteamCMD to download/update the Rust Dedicated Server
// (App ID 258550) into {installDir}/server. This function is platform-agnostic —
// it simply executes the steamcmd binary which was installed by the platform-specific
// InstallSteamCMD function.
func DownloadRustServer(steamcmdPath, installDir string) error {
	serverDir := filepath.Join(installDir, "server")

	log.Printf("Downloading Rust Dedicated Server to %s", serverDir)

	cmd := exec.Command(steamcmdPath,
		"+login", "anonymous",
		"+force_install_dir", serverDir,
		"+app_update", "258550", "validate",
		"+quit",
	)

	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run(); err != nil {
		return fmt.Errorf("steamcmd app_update 258550 failed: %w", err)
	}

	return nil
}

// CheckServerInstalled returns true if the Rust Dedicated Server executable
// exists at the expected path within the install directory.
func CheckServerInstalled(installDir string) bool {
	var exePath string
	switch runtime.GOOS {
	case "windows":
		exePath = filepath.Join(installDir, "server", "RustDedicated.exe")
	default:
		exePath = filepath.Join(installDir, "server", "RustDedicated")
	}

	_, err := os.Stat(exePath)
	return err == nil
}

// publishStatus publishes a DeployStatus message to the NATS subject
// corrosion.{licenseID}.deploy.status. Publish errors are logged but do not
// fail the deployment — losing a progress update is not fatal.
func (d *Deployer) publishStatus(stage string, progress int, message string, errDetail ...string) {
	subject := fmt.Sprintf("corrosion.%s.deploy.status", d.licenseID)

	status := DeployStatus{
		Stage:     stage,
		Progress:  progress,
		Message:   message,
		Timestamp: time.Now().UTC().Format(time.RFC3339),
	}

	if len(errDetail) > 0 && errDetail[0] != "" {
		status.Error = errDetail[0]
	}

	data, err := json.Marshal(status)
	if err != nil {
		log.Printf("Failed to marshal deploy status: %v", err)
		return
	}

	if err := d.nc.Publish(subject, data); err != nil {
		log.Printf("Failed to publish deploy status to %s: %v", subject, err)
	}
}
