package lifecycle

import (
	"context"
	"errors"
	"fmt"
	"os"
	"strings"

	"github.com/tidbcloud/controlplane9/internal/cluster"
	"github.com/tidbcloud/controlplane9/internal/config"
	"github.com/tidbcloud/controlplane9/internal/host"
	cptilt "github.com/tidbcloud/controlplane9/internal/tilt"
)

// GCPRuntimeTiltStep starts the second remote Tilt controller after the shared
// Session Tiltfile context has been written for the GCP host.
type GCPRuntimeTiltStep struct {
	PrimaryStateDir string
}

func (s *GCPRuntimeTiltStep) RunWithState(ctx context.Context, state *cluster.State) error {
	if s == nil || strings.TrimSpace(s.PrimaryStateDir) == "" || state == nil {
		return fmt.Errorf("GCP runtime Tilt requires primary lifecycle state")
	}
	resolved, err := config.LoadSession(resolvedSessionPathForState(s.PrimaryStateDir, state))
	if err != nil {
		return fmt.Errorf("GCP runtime Tilt: load resolved session: %w", err)
	}
	if !config.DedicatedGCPEnabled(resolved) {
		return nil
	}
	gcpStateDir := host.GCPRegionalStateDir(s.PrimaryStateDir)
	paths := cptilt.PathsForState(gcpStateDir, nil)
	report, err := cptilt.LoadContextReport(paths.ContextReport)
	if err != nil {
		return fmt.Errorf("GCP runtime Tilt: load context report: %w", err)
	}
	paths.Tiltfile = resolveTiltReportFilePath(gcpStateDir, report.TiltfilePath)
	index, err := cptilt.LoadContextIndex(paths.ContextIndex)
	if err != nil {
		return fmt.Errorf("GCP runtime Tilt: load context index: %w", err)
	}
	if err := validateGeneratedTiltArtifacts(gcpStateDir, &paths, report); err != nil {
		return fmt.Errorf("GCP runtime Tilt: %w", err)
	}
	selected, err := host.Load(gcpStateDir)
	if err != nil {
		return fmt.Errorf("GCP runtime Tilt: load GCP host: %w", err)
	}
	port, err := cptilt.GCPRuntimeAPIPort(resolved.Tilt.Port)
	if err != nil {
		return fmt.Errorf("GCP runtime Tilt: %w", err)
	}
	if running, err := observeRunningGCPController(ctx, selected, paths.Status); err != nil {
		return err
	} else if running {
		status, err := cptilt.LoadStatus(paths.Status)
		if err != nil {
			return fmt.Errorf("GCP runtime Tilt: reload running status: %w", err)
		}
		if _, err := cptilt.RefreshRemoteControllerInputs(ctx, selected, gcpStateDir, status); err != nil {
			return fmt.Errorf("GCP runtime Tilt: refresh running controller inputs: %w", err)
		}
		return nil
	}

	status, err := cptilt.BuildStatus(gcpStateDir, &paths, resolved.Tilt.Mode, report, index)
	if err != nil {
		return fmt.Errorf("GCP runtime Tilt: build status: %w", err)
	}
	status.APIHost = "127.0.0.1"
	status.APIPort = port
	switch resolved.Tilt.Mode {
	case "generate-only":
		return cptilt.SaveStatus(paths.Status, status)
	case "ci", "up":
		materialized, err := cptilt.MaterializeRemoteController(
			ctx,
			selected,
			paths.Tiltfile,
			paths.ContextIndex,
			&cptilt.Request{Port: port},
		)
		if err != nil {
			return fmt.Errorf("GCP runtime Tilt: %w", err)
		}
		applyRemoteControllerStatus(status, materialized)
		if resolved.Tilt.Mode == "ci" {
			if err := cptilt.RunRemoteCI(ctx, selected, materialized); err != nil {
				return fmt.Errorf("GCP runtime Tilt: %w", err)
			}
			return cptilt.SaveStatus(paths.Status, status)
		}
		if err := cptilt.SaveStatus(paths.Status, status); err != nil {
			return fmt.Errorf("GCP runtime Tilt: persist materialized status: %w", err)
		}
		started, err := cptilt.StartRemoteController(ctx, selected, materialized)
		if err != nil {
			return fmt.Errorf("GCP runtime Tilt: %w", err)
		}
		status.Running = true
		status.PID = started.PID
		status.Command = append([]string(nil), started.Command...)
		startedAt := started.StartedAt.UTC()
		status.StartedAt = &startedAt
		if err := cptilt.SaveStatus(paths.Status, status); err != nil {
			return fmt.Errorf("GCP runtime Tilt: persist status: %w", err)
		}
		return persistGCPControllerConfigured(selected)
	default:
		return fmt.Errorf("GCP runtime Tilt: unsupported mode %q", resolved.Tilt.Mode)
	}
}

func observeRunningGCPController(ctx context.Context, selected *host.Session, statusPath string) (bool, error) {
	status, err := cptilt.LoadStatus(statusPath)
	if errors.Is(err, os.ErrNotExist) {
		return false, nil
	}
	if err != nil {
		return false, fmt.Errorf("GCP runtime Tilt: load persisted status: %w", err)
	}
	if status.Mode != "up" || status.Controller != cptilt.ControllerRemote {
		return false, nil
	}
	observation, err := cptilt.ObserveRemoteController(ctx, selected, status)
	if err != nil {
		return false, fmt.Errorf("GCP runtime Tilt: observe controller: %w", err)
	}
	if !observation.Running() {
		return false, nil
	}
	status.Running = true
	status.PID = observation.PID
	if err := cptilt.SaveStatus(statusPath, status); err != nil {
		return false, fmt.Errorf("GCP runtime Tilt: persist observation: %w", err)
	}
	if err := persistGCPControllerConfigured(selected); err != nil {
		return false, err
	}
	return true, nil
}

func persistGCPControllerConfigured(selected *host.Session) error {
	selected.Execution = selected.ExecutionFacts()
	selected.Execution.Controller = cptilt.ControllerRemote
	if err := host.Save(selected.StateDir, selected); err != nil {
		return fmt.Errorf("GCP runtime Tilt: persist remote controller ownership: %w", err)
	}
	return nil
}
