package tunnel

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"os/exec"
	"strings"
	"time"

	cpaws "github.com/tidbcloud/controlplane9/internal/aws"
	"github.com/tidbcloud/controlplane9/internal/cluster"
	"github.com/tidbcloud/controlplane9/internal/config"
	"github.com/tidbcloud/controlplane9/internal/verbose"
)

const defaultEgressProbeURL = "https://api.ipify.org"

// UpStep performs the initial C1 tunnel readiness check and records the result.
type UpStep struct {
	StateDir       string
	Config         config.TunnelConfig
	Profile        string
	Region         string
	OwnerAllowlist map[string][]string
	STSClient      cpaws.STSAPI
	HTTPClient     *http.Client
	CommandRunner  func(cmd *exec.Cmd) ([]byte, error)
	EgressProbeURL string
}

func (s *UpStep) Name() cluster.StepName { return cluster.StepTunnelUp }
func (s *UpStep) Run(_ context.Context) error {
	return fmt.Errorf("tunnel_up requires lifecycle state")
}

func (s *UpStep) RunWithState(ctx context.Context, lifecycle *cluster.State) error {
	if strings.TrimSpace(s.Config.Mechanism) == "" {
		return fmt.Errorf("tunnel.mechanism is required for %s", cluster.StepTunnelUp)
	}

	cfg, err := cpaws.LoadAWSConfig(ctx, s.Profile, s.Region)
	if err != nil {
		return fmt.Errorf("tunnel_up: load AWS config: %w", err)
	}
	stsClient := s.STSClient
	if stsClient == nil {
		stsClient = cpaws.NewSTSClient(&cfg)
	}
	identity, err := cpaws.ValidateAccess(ctx, stsClient, s.Profile, s.Region)
	if err != nil {
		return fmt.Errorf("tunnel_up: %w", err)
	}
	owner, err := cpaws.ResolveOwnerIdentity(identity, s.OwnerAllowlist)
	if err != nil {
		return fmt.Errorf("tunnel_up: %w", err)
	}

	if err := s.checkMechanism(ctx); err != nil {
		return fmt.Errorf("tunnel_up: %w", err)
	}

	tunnelState := &State{
		Mechanism: s.Config.Mechanism,
		Owner:     owner,
		Profile:   s.Profile,
		Region:    s.Region,
		Account:   identity.Account,
		Healthy:   true,
	}

	if strings.TrimSpace(s.Config.VPCA.ExpectedEgressEIP) != "" {
		observed, err := s.probeEgressIP(ctx)
		if err != nil {
			return fmt.Errorf("tunnel_up: probe egress IP: %w", err)
		}
		tunnelState.ObservedEgressIP = observed
		if observed != strings.TrimSpace(s.Config.VPCA.ExpectedEgressEIP) {
			return fmt.Errorf("tunnel_up: observed egress IP %q does not match expected %q",
				observed, s.Config.VPCA.ExpectedEgressEIP)
		}
	}

	if err := SaveState(s.StateDir, tunnelState); err != nil {
		return fmt.Errorf("tunnel_up: save state: %w", err)
	}

	lifecycle.Owner = owner
	return nil
}

func (s *UpStep) checkMechanism(ctx context.Context) error {
	switch strings.TrimSpace(s.Config.Mechanism) {
	case "tailscale":
		cmd := exec.CommandContext(ctx, "tailscale", "status", "--json")
		if _, err := s.runCommand(cmd); err != nil {
			return fmt.Errorf("tailscale status: %w", err)
		}
		return nil
	default:
		return fmt.Errorf("unsupported tunnel mechanism %q", s.Config.Mechanism)
	}
}

func (s *UpStep) probeEgressIP(ctx context.Context) (string, error) {
	url := s.EgressProbeURL
	if url == "" {
		url = defaultEgressProbeURL
	}
	client := s.HTTPClient
	if client == nil {
		client = verbose.NewHTTPClient(10 * time.Second)
	}
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
	if err != nil {
		return "", err
	}
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("unexpected status %d", resp.StatusCode)
	}
	return strings.TrimSpace(string(body)), nil
}

func (s *UpStep) runCommand(cmd *exec.Cmd) ([]byte, error) {
	if s.CommandRunner != nil {
		return s.CommandRunner(cmd)
	}
	return verbose.RunCommand(cmd)
}
