package host

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

	"github.com/tidbcloud/controlplane9/internal/config"
	"github.com/tidbcloud/controlplane9/internal/host/remoteimage"
)

const (
	defaultStateRoot        = ".cp9"
	kubeconfigFileName      = "kubeconfig.yaml"
	selectedHostJSON        = "host.json"
	gcpRegionalStateDirName = "gcp-regional"

	SSHTransportDirect    = "direct"
	SSHTransportSSM       = "ssm"
	SSHTransportGCloudIAP = "gcloud-iap"

	RemoteNodeStateDir             = "/cp9/session-state"
	GCPAWSWebIdentityTokenNodePath = RemoteNodeStateDir + "/identity/token"
)

// Session captures the selected host contract for a cp9 session.
type Session struct {
	Mode           config.HostMode      `json:"mode"`
	StateDir       string               `json:"state_dir"`
	ClusterName    string               `json:"cluster_name"`
	Namespace      string               `json:"namespace"`
	KubeconfigPath string               `json:"kubeconfig_path"`
	Context        string               `json:"context"`
	Profile        string               `json:"profile,omitempty"`
	Region         string               `json:"region,omitempty"`
	Remote         *RemoteSession       `json:"remote,omitempty"`
	Execution      *ExecutionFacts      `json:"execution,omitempty"`
	DedicatedAPI   *DedicatedAPISession `json:"dedicated_api,omitempty"`
	GCP            *GCPRemoteSession    `json:"gcp,omitempty"`
}

// DedicatedAPISession captures the independent metadata/control API embedded
// in a dev-full host cluster. Workloads still deploy through Session's host
// kubeconfig; these fields identify the API those workloads control.
type DedicatedAPISession struct {
	HostPort                  int    `json:"host_port"`
	NodePort                  int    `json:"node_port"`
	InternalEndpoint          string `json:"internal_endpoint"`
	LocalEndpoint             string `json:"local_endpoint"`
	AdminKubeconfigPath       string `json:"admin_kubeconfig_path"`
	InternalKubeconfigPath    string `json:"internal_kubeconfig_path"`
	RemoteAdminKubeconfigPath string `json:"remote_admin_kubeconfig_path,omitempty"`
	PKIDir                    string `json:"pki_dir"`
	APIServerID               string `json:"api_server_id"`
	EtcdMemberID              string `json:"etcd_member_id"`
	KubernetesVersion         string `json:"kubernetes_version,omitempty"`
	APIServerImage            string `json:"api_server_image,omitempty"`
	ControllerManagerImage    string `json:"controller_manager_image,omitempty"`
	EtcdImage                 string `json:"etcd_image,omitempty"`
	CAFingerprint             string `json:"ca_fingerprint,omitempty"`
	DefinitionDigest          string `json:"definition_digest,omitempty"`
	HostManifestPath          string `json:"host_manifest_path,omitempty"`
	BootstrapManifestPath     string `json:"bootstrap_manifest_path,omitempty"`
}

// RemoteSession captures provider-neutral SSH/k3d connection metadata plus
// the existing AWS-specific exposure fields persisted in host.json.
type RemoteSession struct {
	Profile                        string                           `json:"profile,omitempty"`
	Region                         string                           `json:"region"`
	InstanceID                     string                           `json:"instance_id"`
	InstanceName                   string                           `json:"instance_name,omitempty"`
	InstanceProfileName            string                           `json:"instance_profile_name,omitempty"`
	RuntimeRoleARN                 string                           `json:"runtime_role_arn,omitempty"`
	PublicIP                       string                           `json:"public_ip,omitempty"`
	PublicDNS                      string                           `json:"public_dns,omitempty"`
	PublicLoadBalancerARN          string                           `json:"public_load_balancer_arn,omitempty"`
	PublicLoadBalancerDNS          string                           `json:"public_load_balancer_dns,omitempty"`
	PublicLoadBalancerTargetGroup  string                           `json:"public_load_balancer_target_group,omitempty"`
	PublicLoadBalancerListener     *RemoteLoadBalancerListener      `json:"public_load_balancer_listener,omitempty"`
	PublicLoadBalancerTargets      []RemoteLoadBalancerTarget       `json:"public_load_balancer_targets,omitempty"`
	PublicLoadBalancerTargetHealth []RemoteLoadBalancerTargetHealth `json:"public_load_balancer_target_health,omitempty"`
	O11YLoadBalancerARN            string                           `json:"o11y_load_balancer_arn,omitempty"`
	O11YLoadBalancerDNS            string                           `json:"o11y_load_balancer_dns,omitempty"`
	O11YLoadBalancerTargetGroup    string                           `json:"o11y_load_balancer_target_group,omitempty"`
	O11YLoadBalancerListener       *RemoteLoadBalancerListener      `json:"o11y_load_balancer_listener,omitempty"`
	O11YLoadBalancerTargets        []RemoteLoadBalancerTarget       `json:"o11y_load_balancer_targets,omitempty"`
	O11YEndpointServiceID          string                           `json:"o11y_endpoint_service_id,omitempty"`
	O11YEndpointServiceName        string                           `json:"o11y_endpoint_service_name,omitempty"`
	O11YEndpointServiceSourceCIDR  string                           `json:"o11y_endpoint_service_source_cidr,omitempty"`
	PublicHTTPSLoadBalancerARN     string                           `json:"public_https_load_balancer_arn,omitempty"`
	PublicHTTPSLoadBalancerDNS     string                           `json:"public_https_load_balancer_dns,omitempty"`
	PublicHTTPSTargetGroup         string                           `json:"public_https_target_group,omitempty"`
	PublicHTTPSListener            *RemoteLoadBalancerListener      `json:"public_https_listener,omitempty"`
	PublicHTTPSRecordName          string                           `json:"public_https_record_name,omitempty"`
	PublicHTTPSRecordZoneID        string                           `json:"public_https_record_zone_id,omitempty"`
	PublicHTTPSRecordRoleARN       string                           `json:"public_https_record_role_arn,omitempty"`
	PublicHTTPSSecurityGroup       string                           `json:"public_https_security_group,omitempty"`
	PublicHTTPSURL                 string                           `json:"public_https_url,omitempty"`
	SSHUser                        string                           `json:"ssh_user"`
	SSHPrivateKeyPath              string                           `json:"ssh_private_key_path"`
	SSHTransport                   string                           `json:"ssh_transport,omitempty"`
	RemoteStateDir                 string                           `json:"remote_state_dir"`
	RemoteKubeconfigPath           string                           `json:"remote_kubeconfig_path"`
	RemoteSecurityGroup            string                           `json:"remote_security_group,omitempty"`
	CPUCount                       int                              `json:"cpu_count,omitempty"`
	Architecture                   string                           `json:"architecture,omitempty"`
	Image                          *remoteimage.Plan                `json:"image,omitempty"`
}

// GCPRemoteSession captures non-secret provider facts for the secondary GCE
// host. Instance identity and address stay in RemoteSession as the one shared
// remote-host fact.
type GCPRemoteSession struct {
	ProjectID               string `json:"project_id"`
	DataplaneProjectID      string `json:"dataplane_project_id"`
	Zone                    string `json:"zone"`
	ServiceAccountEmail     string `json:"service_account_email,omitempty"`
	ServiceAccountUniqueID  string `json:"service_account_unique_id,omitempty"`
	AWSRoleARN              string `json:"aws_role_arn,omitempty"`
	RegionalEndpointHost    string `json:"regional_endpoint_host,omitempty"`
	RegionalEndpointURL     string `json:"regional_endpoint_url,omitempty"`
	AWSWebIdentityTokenFile string `json:"aws_web_identity_token_file,omitempty"`
	Network                 string `json:"network,omitempty"`
	Subnet                  string `json:"subnet,omitempty"`
	O11YServiceAttachment   string `json:"o11y_service_attachment,omitempty"`
	O11YBucket              string `json:"o11y_bucket,omitempty"`
}

// ExecutionFacts names which machine owns each host-mode execution boundary.
type ExecutionFacts struct {
	Mode                  config.HostMode       `json:"mode"`
	Controller            string                `json:"controller"`
	WorkloadRuntime       string                `json:"workload_runtime"`
	SourceSync            string                `json:"source_sync"`
	SourceSyncDestination string                `json:"source_sync_destination,omitempty"`
	ModuleProxy           string                `json:"module_proxy"`
	DockerBuild           string                `json:"docker_build"`
	ImageImport           string                `json:"image_import"`
	K3dRuntime            string                `json:"k3d_runtime"`
	KubeAPITunnel         string                `json:"kube_api_tunnel,omitempty"`
	LocalKubeconfigPath   string                `json:"local_kubeconfig_path,omitempty"`
	KubeContext           string                `json:"kube_context,omitempty"`
	RemoteKubeconfigPath  string                `json:"remote_kubeconfig_path,omitempty"`
	Remote                *RemoteExecutionFacts `json:"remote,omitempty"`
}

// RemoteExecutionFacts captures the remote endpoint fields agents need when
// proving which remote host owns an execution boundary.
type RemoteExecutionFacts struct {
	Profile              string            `json:"profile,omitempty"`
	Region               string            `json:"region,omitempty"`
	InstanceID           string            `json:"instance_id,omitempty"`
	InstanceName         string            `json:"instance_name,omitempty"`
	PublicIP             string            `json:"public_ip,omitempty"`
	PublicDNS            string            `json:"public_dns,omitempty"`
	SSHUser              string            `json:"ssh_user,omitempty"`
	SSHTransport         string            `json:"ssh_transport,omitempty"`
	RemoteStateDir       string            `json:"remote_state_dir,omitempty"`
	RemoteKubeconfigPath string            `json:"remote_kubeconfig_path,omitempty"`
	RemoteSecurityGroup  string            `json:"remote_security_group,omitempty"`
	Architecture         string            `json:"architecture,omitempty"`
	Image                *remoteimage.Plan `json:"image,omitempty"`
}

// RemoteLoadBalancerListener captures the public NLB listener that forwards to
// the remote EC2 host target group.
type RemoteLoadBalancerListener struct {
	ARN            string `json:"arn,omitempty"`
	Port           int32  `json:"port,omitempty"`
	Protocol       string `json:"protocol,omitempty"`
	TargetGroupARN string `json:"target_group_arn,omitempty"`
}

// RemoteLoadBalancerTarget captures a registered NLB target.
type RemoteLoadBalancerTarget struct {
	ID   string `json:"id"`
	Port int32  `json:"port,omitempty"`
}

// RemoteLoadBalancerTargetHealth captures the latest ELBv2 target health
// reported for a registered public NLB target.
type RemoteLoadBalancerTargetHealth struct {
	ID          string `json:"id"`
	Port        int32  `json:"port,omitempty"`
	State       string `json:"state,omitempty"`
	Reason      string `json:"reason,omitempty"`
	Description string `json:"description,omitempty"`
}

// Resolve derives the selected host contract from the current config.
func Resolve(cfg *config.Config, local *config.LocalConfig) *Session {
	mode := config.HostModeLocalK3d
	if local != nil && local.Host.Mode != "" {
		mode = local.Host.Mode
	}

	stateDir := filepath.Join(defaultStateRoot, cfg.Name)
	session := &Session{
		Mode:           mode,
		StateDir:       stateDir,
		ClusterName:    cfg.Name,
		Namespace:      cfg.Name,
		KubeconfigPath: filepath.Join(stateDir, kubeconfigFileName),
	}

	switch mode {
	case config.HostModeLocalK3d:
		session.Context = "k3d-" + cfg.Name
	case config.HostModeRemoteEC2K3d:
		session.Context = "k3d-" + cfg.Name
	default:
		session.Context = cfg.Name
	}

	return session
}

// ResolveSessionConfig derives the selected host contract from a v1alpha2 session config.
func ResolveSessionConfig(cfg *config.SessionConfig) *Session {
	mode := cfg.Host.Mode
	if mode == "" {
		mode = config.HostModeLocalK3d
	}

	stateDir := filepath.Join(defaultStateRoot, cfg.Name)
	session := &Session{
		Mode:           mode,
		StateDir:       stateDir,
		ClusterName:    cfg.Name,
		Namespace:      cfg.Name,
		KubeconfigPath: filepath.Join(stateDir, kubeconfigFileName),
	}

	switch mode {
	case config.HostModeLocalK3d, config.HostModeRemoteEC2K3d:
		session.Context = "k3d-" + cfg.Name
	default:
		session.Context = cfg.Name
	}

	return session
}

// GCPRegionalStateDir returns the independent host-state root for the optional
// Dedicated GCP regional host.
func GCPRegionalStateDir(stateDir string) string {
	return filepath.Join(stateDir, gcpRegionalStateDirName)
}

// StatePath returns the persisted selected-host contract path for any host
// state root.
func StatePath(stateDir string) string { return filepath.Join(stateDir, selectedHostJSON) }

// ResolveGCPRegionalSession derives the secondary GCE+k3d host contract. It
// never changes or replaces the primary selected host.
func ResolveGCPRegionalSession(cfg *config.SessionConfig, primaryStateDir string) *Session {
	if !config.DedicatedGCPEnabled(cfg) {
		return nil
	}
	clusterName := cfg.Name + "-gcp-regional"
	stateDir := GCPRegionalStateDir(primaryStateDir)
	gcp := cfg.Dedicated.GCP
	return &Session{
		Mode:           config.HostModeRemoteGCEK3d,
		StateDir:       stateDir,
		ClusterName:    clusterName,
		Namespace:      clusterName,
		KubeconfigPath: filepath.Join(stateDir, kubeconfigFileName),
		Context:        "k3d-" + clusterName,
		Region:         gcp.Region,
		Remote: &RemoteSession{
			Region:               gcp.Region,
			SSHTransport:         SSHTransportGCloudIAP,
			RemoteStateDir:       "~/.cp9/" + clusterName,
			RemoteKubeconfigPath: "~/.cp9/" + clusterName + "/kubeconfig.yaml",
		},
		GCP: &GCPRemoteSession{
			ProjectID:          gcp.SeedProjectID,
			DataplaneProjectID: gcp.DataplaneProjectID,
			Zone:               gcp.Zone,
		},
	}
}

// LoadOrResolve returns the persisted selected-host contract when present, or
// resolves a fresh one from config when the session has not been provisioned yet.
func LoadOrResolve(cfg *config.Config, local *config.LocalConfig) (*Session, error) {
	resolved := Resolve(cfg, local)
	loaded, err := Load(resolved.StateDir)
	if err == nil {
		return loaded, nil
	}
	if os.IsNotExist(err) {
		return resolved, nil
	}
	return nil, err
}

// KubectlArgs prefixes kubectl arguments with the selected kubeconfig/context.
func (s *Session) KubectlArgs(args ...string) []string {
	if s == nil {
		return append([]string(nil), args...)
	}
	var prefixed []string
	if s.KubeconfigPath != "" {
		prefixed = append(prefixed, "--kubeconfig", s.KubeconfigPath)
	}
	if s.Context != "" {
		prefixed = append(prefixed, "--context", s.Context)
	}
	return append(prefixed, args...)
}

// KubectlEnv returns selected-host environment variables needed by local
// kubectl subprocesses, including kubeconfig exec credential plugins.
func (s *Session) KubectlEnv() []string {
	if s == nil {
		return nil
	}
	var env []string
	if s.Profile != "" {
		env = append(env, "AWS_PROFILE="+s.Profile)
	}
	if s.Region != "" {
		env = append(env, "AWS_REGION="+s.Region, "AWS_DEFAULT_REGION="+s.Region)
	}
	return env
}

// IsRemote reports whether the selected host runs on a remote VM.
func (s *Session) IsRemote() bool {
	return s != nil && s.Remote != nil &&
		(s.Mode == config.HostModeRemoteEC2K3d || s.Mode == config.HostModeRemoteGCEK3d)
}

// IsRemoteEC2 reports whether the selected host is the primary AWS runtime.
func (s *Session) IsRemoteEC2() bool {
	return s != nil && s.Mode == config.HostModeRemoteEC2K3d && s.Remote != nil
}

// ExecutionFacts derives the selected-host execution contract from the session.
func (s *Session) ExecutionFacts() *ExecutionFacts {
	if s == nil {
		return nil
	}
	facts := &ExecutionFacts{
		Mode:                  s.Mode,
		Controller:            "local",
		WorkloadRuntime:       string(s.Mode),
		SourceSync:            "local",
		SourceSyncDestination: s.StateDir,
		ModuleProxy:           "local",
		DockerBuild:           "local",
		ImageImport:           "local",
		K3dRuntime:            "local",
		LocalKubeconfigPath:   s.KubeconfigPath,
		KubeContext:           s.Context,
	}
	if facts.WorkloadRuntime == "" {
		facts.WorkloadRuntime = string(config.HostModeLocalK3d)
	}
	if !s.IsRemote() {
		return facts
	}

	facts.Controller = "remote"
	facts.WorkloadRuntime = string(s.Mode)
	facts.SourceSync = "local-to-remote"
	facts.SourceSyncDestination = ""
	facts.ModuleProxy = "remote"
	facts.DockerBuild = "remote"
	facts.ImageImport = "remote"
	facts.K3dRuntime = "remote"
	facts.KubeAPITunnel = firstNonEmpty(s.Remote.SSHTransport, SSHTransportDirect)
	if s.Remote == nil {
		return facts
	}
	facts.SourceSyncDestination = s.Remote.RemoteStateDir
	facts.RemoteKubeconfigPath = s.Remote.RemoteKubeconfigPath
	facts.Remote = &RemoteExecutionFacts{
		Profile:              firstNonEmpty(s.Remote.Profile, s.Profile),
		Region:               firstNonEmpty(s.Remote.Region, s.Region),
		InstanceID:           s.Remote.InstanceID,
		InstanceName:         s.Remote.InstanceName,
		PublicIP:             s.Remote.PublicIP,
		PublicDNS:            s.Remote.PublicDNS,
		SSHUser:              s.Remote.SSHUser,
		SSHTransport:         firstNonEmpty(s.Remote.SSHTransport, SSHTransportDirect),
		RemoteStateDir:       s.Remote.RemoteStateDir,
		RemoteKubeconfigPath: s.Remote.RemoteKubeconfigPath,
		RemoteSecurityGroup:  s.Remote.RemoteSecurityGroup,
		Architecture:         s.Remote.Architecture,
		Image:                s.Remote.Image,
	}
	if s.Mode == config.HostModeRemoteGCEK3d {
		facts.Controller = "unconfigured"
		if s.Execution != nil && s.Execution.Controller == "remote" {
			facts.Controller = "remote"
		}
		facts.WorkloadRuntime = string(s.Mode)
		facts.SourceSync = "runtime-bundle-only"
		facts.SourceSyncDestination = s.Remote.RemoteStateDir
		facts.ModuleProxy = "unsupported"
		facts.DockerBuild = "unsupported"
		facts.ImageImport = "immutable-owner-only"
	}
	return facts
}

// Save persists the selected host contract under the session state directory.
func Save(stateDir string, session *Session) error {
	if session == nil {
		return fmt.Errorf("selected host session is nil")
	}
	if err := os.MkdirAll(stateDir, 0o755); err != nil {
		return fmt.Errorf("create host state dir: %w", err)
	}
	persisted := *session
	persisted.StateDir = stateDir
	persisted.Execution = persisted.ExecutionFacts()
	data, err := json.MarshalIndent(&persisted, "", "  ")
	if err != nil {
		return fmt.Errorf("marshal host session: %w", err)
	}
	if err := os.WriteFile(StatePath(stateDir), data, 0o600); err != nil {
		return fmt.Errorf("write host session: %w", err)
	}
	return nil
}

// Load reads the persisted selected host contract.
func Load(stateDir string) (*Session, error) {
	data, err := os.ReadFile(StatePath(stateDir))
	if err != nil {
		return nil, err
	}
	var session Session
	if err := json.Unmarshal(data, &session); err != nil {
		return nil, fmt.Errorf("parse host session: %w", err)
	}
	if session.Execution == nil {
		session.Execution = session.ExecutionFacts()
	}
	return &session, nil
}
