package tilt

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/tidbcloud/controlplane9/internal/cluster"
)

const (
	DefaultAPIHost = "localhost"
	DefaultAPIPort = 10350
)

// Status is the persisted Tilt runtime contract for one cp9 session.
type Status struct {
	RuntimeID                 string        `json:"runtime_id,omitempty"`
	SessionName               string        `json:"session_name"`
	Owner                     string        `json:"owner"`
	Namespace                 string        `json:"namespace"`
	Mode                      string        `json:"mode"`
	Controller                string        `json:"controller,omitempty"`
	APIHost                   string        `json:"api_host"`
	APIPort                   int           `json:"api_port"`
	Running                   bool          `json:"running"`
	PID                       int           `json:"pid,omitempty"`
	StartedAt                 *time.Time    `json:"started_at,omitempty"`
	KubeAPITunnelPID          int           `json:"kube_api_tunnel_pid,omitempty"`
	KubeAPITunnelCommand      []string      `json:"kube_api_tunnel_command,omitempty"`
	KubeAPITunnelLogPath      string        `json:"kube_api_tunnel_log_path,omitempty"`
	KubeAPITunnelChildPIDPath string        `json:"kube_api_tunnel_child_pid_path,omitempty"`
	KubeAPITunnelStartedAt    *time.Time    `json:"kube_api_tunnel_started_at,omitempty"`
	KubeAPITunnelRepair       *TunnelRepair `json:"kube_api_tunnel_repair,omitempty"`
	TiltfilePath              string        `json:"tiltfile_path"`
	ContextReportPath         string        `json:"context_report_path"`
	ContextIndexPath          string        `json:"context_index_path"`
	LogPath                   string        `json:"log_path,omitempty"`
	Command                   []string      `json:"command,omitempty"`
	RemoteUnit                string        `json:"remote_unit,omitempty"`
	RemoteRoot                string        `json:"remote_root,omitempty"`
	RemoteTiltfilePath        string        `json:"remote_tiltfile_path,omitempty"`
	RemoteContextIndexPath    string        `json:"remote_context_index_path,omitempty"`
	RemoteKubeconfigPath      string        `json:"remote_kubeconfig_path,omitempty"`
	ControllerTunnelPID       int           `json:"controller_tunnel_pid,omitempty"`
	ControllerTunnelCommand   []string      `json:"controller_tunnel_command,omitempty"`
	ControllerTunnelLogPath   string        `json:"controller_tunnel_log_path,omitempty"`
	ControllerTunnelChildPID  string        `json:"controller_tunnel_child_pid_path,omitempty"`
	ControllerTunnelStartedAt *time.Time    `json:"controller_tunnel_started_at,omitempty"`
}

type TunnelRepair struct {
	Status       string     `json:"status"`
	Message      string     `json:"message,omitempty"`
	FailureClass string     `json:"failure_class,omitempty"`
	Attempts     int        `json:"attempts,omitempty"`
	OldPID       int        `json:"old_pid,omitempty"`
	NewPID       int        `json:"new_pid,omitempty"`
	StartedAt    *time.Time `json:"started_at,omitempty"`
	CompletedAt  *time.Time `json:"completed_at,omitempty"`
}

// BuildStatus merges the session context report and index into the persisted
// Tilt runtime contract used by later day-2 commands.
func BuildStatus(stateDir string, paths *Paths, mode string, report *ContextReport, index *ContextIndex) (*Status, error) {
	if report == nil {
		return nil, fmt.Errorf("tilt status: context report is required")
	}
	if index == nil {
		return nil, fmt.Errorf("tilt status: context index is required")
	}
	if paths == nil {
		return nil, fmt.Errorf("tilt status: tilt paths are required")
	}
	status := &Status{
		RuntimeID:         firstNonEmpty(report.RuntimeID, index.RuntimeID),
		SessionName:       firstNonEmpty(report.SessionName, index.SessionName),
		Owner:             firstNonEmpty(report.Owner, index.Owner),
		Namespace:         firstNonEmpty(report.Namespace, index.Namespace),
		Mode:              strings.TrimSpace(mode),
		Controller:        "local",
		APIHost:           DefaultAPIHost,
		APIPort:           DefaultAPIPort,
		TiltfilePath:      firstNonEmpty(report.TiltfilePath, sessionRelativePath(stateDir, paths.Tiltfile)),
		ContextReportPath: sessionRelativePath(stateDir, paths.ContextReport),
		ContextIndexPath:  firstNonEmpty(report.ContextPath, sessionRelativePath(stateDir, paths.ContextIndex)),
	}
	return status, nil
}

// SaveStatus writes the persisted Tilt runtime status to disk atomically.
func SaveStatus(path string, status *Status) error {
	if status == nil {
		return fmt.Errorf("tilt status is nil")
	}
	if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
		return fmt.Errorf("create tilt status dir: %w", err)
	}
	data, err := json.MarshalIndent(status, "", "  ")
	if err != nil {
		return fmt.Errorf("marshal tilt status: %w", err)
	}
	tempFile, err := os.CreateTemp(filepath.Dir(path), "tilt-status.*.tmp")
	if err != nil {
		return fmt.Errorf("create tilt status temp file: %w", err)
	}
	tempPath := tempFile.Name()
	defer os.Remove(tempPath)
	if _, err := tempFile.Write(data); err != nil {
		_ = tempFile.Close()
		return fmt.Errorf("write tilt status temp file: %w", err)
	}
	if err := tempFile.Close(); err != nil {
		return fmt.Errorf("close tilt status temp file: %w", err)
	}
	if err := os.Rename(tempPath, path); err != nil {
		return fmt.Errorf("rename tilt status file: %w", err)
	}
	return nil
}

// LoadStatus reads persisted Tilt runtime status from disk.
func LoadStatus(path string) (*Status, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("read tilt status: %w", err)
	}
	var status Status
	if err := json.Unmarshal(data, &status); err != nil {
		return nil, fmt.Errorf("parse tilt status: %w", err)
	}
	if strings.TrimSpace(status.APIHost) == "" {
		status.APIHost = DefaultAPIHost
	}
	if status.APIPort <= 0 {
		status.APIPort = DefaultAPIPort
	}
	return &status, nil
}

// LoadStatusForState resolves and reads persisted Tilt runtime status for one
// lifecycle state.
func LoadStatusForState(stateDir string, state *cluster.State) (*Status, error) {
	return LoadStatus(PathsForState(stateDir, state).Status)
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		value = strings.TrimSpace(value)
		if value != "" {
			return value
		}
	}
	return ""
}
