package lifecycle

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

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

// DedicatedAPIHostReadyStep materializes the second API identity immediately
// before host creation so k3d can bind the persisted loopback port and later
// Tilt resources can consume the same certificates.
type DedicatedAPIHostReadyStep struct {
	Selected        *host.Session
	Inner           cluster.Step
	Now             func() time.Time
	HostGitVersion  func(context.Context, *host.Session) (string, error)
	PortReservation *dedicatedapi.PortReservation
}

func (s *DedicatedAPIHostReadyStep) Name() cluster.StepName { return cluster.StepClusterCreate }

func (s *DedicatedAPIHostReadyStep) Run(ctx context.Context) error {
	if s.Selected == nil || s.Selected.DedicatedAPI == nil {
		return fmt.Errorf("dedicated API host readiness requires prepared session state")
	}
	now := time.Now().UTC()
	if s.Now != nil {
		now = s.Now().UTC()
	}
	if err := dedicatedapi.MaterializePKI(s.Selected, now); err != nil {
		return err
	}
	if s.Inner == nil {
		return fmt.Errorf("dedicated API host readiness requires a host creation step")
	}
	if err := s.PortReservation.Release(); err != nil {
		return fmt.Errorf("release Dedicated API port reservation: %w", err)
	}
	if err := s.Inner.Run(ctx); err != nil {
		return err
	}
	readVersion := s.HostGitVersion
	if readVersion == nil {
		readVersion = dedicatedAPIHostGitVersion
	}
	gitVersion, err := readVersion(ctx, s.Selected)
	if err != nil {
		return fmt.Errorf("resolve Dedicated API version from host: %w", err)
	}
	images, err := dedicatedapi.ResolveControlPlaneImages(gitVersion)
	if err != nil {
		return err
	}
	state := s.Selected.DedicatedAPI
	state.KubernetesVersion = images.KubernetesVersion
	state.APIServerImage = images.APIServerImage
	state.ControllerManagerImage = images.ControllerManagerImage
	hostManifest, err := dedicatedapi.RenderHostManifest(s.Selected)
	if err != nil {
		return err
	}
	manifestDir := filepath.Join(s.Selected.StateDir, dedicatedapi.StateDirName, "manifests")
	state.HostManifestPath = filepath.Join(manifestDir, "host.yaml")
	state.BootstrapManifestPath = filepath.Join(manifestDir, "bootstrap.yaml")
	if err := writeDedicatedAPIPrivateFile(state.HostManifestPath, hostManifest); err != nil {
		return err
	}
	if err := writeDedicatedAPIPrivateFile(
		state.BootstrapManifestPath,
		dedicatedapi.RenderBootstrapManifest(s.Selected.ClusterName),
	); err != nil {
		return err
	}
	if s.Selected.Remote != nil {
		if err := dedicatedapi.PersistRemoteAdminKubeconfig(ctx, s.Selected); err != nil {
			return err
		}
	}
	if err := host.Save(s.Selected.StateDir, s.Selected); err != nil {
		return fmt.Errorf("persist Dedicated API host contract: %w", err)
	}
	return nil
}

func dedicatedAPIHostGitVersion(ctx context.Context, selected *host.Session) (string, error) {
	versionCtx, cancel := context.WithTimeout(ctx, 2*time.Minute)
	defer cancel()

	for {
		output, err := host.RunKubectl(versionCtx, selected, nil, "version", "-o", "json")
		if err == nil {
			return parseDedicatedAPIHostGitVersion(output)
		}
		if !isTransientDedicatedAPIHostVersionError(err) || versionCtx.Err() != nil {
			return "", err
		}
		if err := sleepUntilRetryAttempt(versionCtx); err != nil {
			return "", err
		}
	}
}

func isTransientDedicatedAPIHostVersionError(err error) bool {
	text := strings.ToLower(err.Error())
	return strings.Contains(text, "error from server (serviceunavailable)") ||
		strings.Contains(text, "the server is currently unable to handle the request") ||
		strings.Contains(text, "unable to connect to the server")
}

func parseDedicatedAPIHostGitVersion(output []byte) (string, error) {
	var document dedicatedAPIKubectlVersion
	if err := json.Unmarshal(output, &document); err != nil {
		return "", fmt.Errorf("parse host kubectl version: %w", err)
	}
	if document.ServerVersion.GitVersion == "" {
		return "", fmt.Errorf("host kubectl version did not include serverVersion.gitVersion")
	}
	return document.ServerVersion.GitVersion, nil
}

type dedicatedAPIKubectlVersion struct {
	ServerVersion dedicatedAPIServerVersion `json:"serverVersion"`
}

type dedicatedAPIServerVersion struct {
	GitVersion string `json:"gitVersion"`
}

func writeDedicatedAPIPrivateFile(path string, data []byte) error {
	if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
		return fmt.Errorf("create Dedicated API manifest directory: %w", err)
	}
	if err := os.WriteFile(path, data, 0o600); err != nil {
		return fmt.Errorf("write Dedicated API manifest %s: %w", path, err)
	}
	if err := os.Chmod(path, 0o600); err != nil {
		return fmt.Errorf("secure Dedicated API manifest %s: %w", path, err)
	}
	return nil
}
