package cmd

import (
	"context"
	"fmt"
	"slices"
	"strings"
	"time"

	awssdk "github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/service/eks"
	"github.com/aws/aws-sdk-go-v2/service/iam"

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

// purgeEKSRoleGraphAPI is the read/tag surface needed to recover an EKS graph
// from a tag-verified, deterministic CP9 service role. The relationship is
// immutable on an EKS cluster (cluster.RoleArn), so it is a stronger boundary
// than a resource-name match.
type purgeEKSRoleGraphAPI interface {
	cpaws.EKSResourceGraphAPI
	TagResource(
		ctx context.Context,
		params *eks.TagResourceInput,
		optFns ...func(*eks.Options),
	) (*eks.TagResourceOutput, error)
}

var purgeNewEKSRoleGraphClient = func(cfg awssdk.Config) purgeEKSRoleGraphAPI {
	return eks.NewFromConfig(cfg)
}

var purgeNewEKSRoleProofClient = func(cfg awssdk.Config) cpaws.EKSRoleProofAPI {
	return iam.NewFromConfig(cfg)
}

// discoverNukeEKSRoleChildren finds clusters whose immutable RoleArn equals the
// deterministic EKS service role. The shared graph predicate independently
// re-reads that role's owner/session/managed tags, then follows the EKS
// ListNodegroups relationship. This function is always strictly read-only.
func discoverNukeEKSRoleChildren(
	ctx context.Context,
	client purgeEKSRoleGraphAPI,
	roleClient cpaws.EKSRoleProofAPI,
	scope cpaws.Scope,
	accountID string,
) ([]cpaws.Resource, []purgeTrustedParentClaim, error) {
	if client == nil || roleClient == nil {
		return nil, nil, fmt.Errorf("EKS trusted-parent EKS and IAM clients are required")
	}
	scope = scope.Normalize()
	if err := scope.Validate(); err != nil {
		return nil, nil, fmt.Errorf("EKS trusted-parent scope: %w", err)
	}
	if scope.Owner == "" || scope.SessionID == "" {
		return nil, nil, fmt.Errorf("EKS trusted-parent discovery requires owner/session scope")
	}
	region := firstNonEmptyString(scope.Regions...)
	graph, err := cpaws.DiscoverTrustedEKSRoleGraph(
		ctx,
		client,
		roleClient,
		scope.Owner,
		scope.SessionID,
		region,
		accountID,
	)
	if err != nil {
		return nil, nil, err
	}
	if graph.RoleARN == "" {
		return nil, nil, nil
	}

	resources := append([]cpaws.Resource(nil), graph.Resources...)
	var claims []purgeTrustedParentClaim
	for _, resource := range resources {
		var claimType string
		switch resource.Type {
		case cpaws.ResourceEKSCluster:
			claimType = "EKSCluster"
		case cpaws.ResourceEKSNodegroup:
			claimType = "EKSNodegroup"
		default:
			return nil, nil, fmt.Errorf("trusted EKS graph returned unsupported resource type %q", resource.Type)
		}
		parent := strings.TrimSpace(graph.ParentByARN[resource.ARN])
		if parent == "" {
			return nil, nil, fmt.Errorf("trusted EKS graph resource %s has no parent proof", resource.ARN)
		}
		claims = append(claims, purgeTrustedParentClaim{Type: claimType, ID: resource.ARN, Parent: parent})
	}

	slices.SortFunc(resources, func(a, b cpaws.Resource) int {
		if a.Type != b.Type {
			return strings.Compare(string(a.Type), string(b.Type))
		}
		return strings.Compare(a.ARN, b.ARN)
	})
	slices.SortFunc(claims, func(a, b purgeTrustedParentClaim) int {
		if a.Type != b.Type {
			return strings.Compare(a.Type, b.Type)
		}
		return strings.Compare(a.ID, b.ID)
	})
	return resources, claims, nil
}

// tagValidatedNukeEKSRoleChildren applies cleanup tags only to a graph that was
// fully validated by discoverNukeEKSRoleChildren. Validate the complete local
// plan before the first outbound mutation so a malformed claim cannot leave a
// partially tagged graph.
func tagValidatedNukeEKSRoleChildren(
	ctx context.Context,
	client purgeEKSRoleGraphAPI,
	resources []cpaws.Resource,
	scope cpaws.Scope,
) error {
	if client == nil {
		return fmt.Errorf("EKS trusted-parent client is required")
	}
	scope = scope.Normalize()
	if err := scope.Validate(); err != nil {
		return fmt.Errorf("EKS trusted-parent tag scope: %w", err)
	}
	if scope.Owner == "" || scope.SessionID == "" {
		return fmt.Errorf("EKS trusted-parent tagging requires owner/session scope")
	}
	for _, resource := range resources {
		if resource.Type != cpaws.ResourceEKSCluster && resource.Type != cpaws.ResourceEKSNodegroup {
			return fmt.Errorf("trusted EKS tag plan contains unsupported resource type %q", resource.Type)
		}
		if strings.TrimSpace(resource.ARN) == "" ||
			!cpaws.OwnerTagMatches(scope.Owner, resource.Owner) ||
			strings.TrimSpace(resource.SessionID) != scope.SessionID {
			return fmt.Errorf(
				"trusted EKS tag plan contains ambiguous resource arn=%q owner=%q session=%q",
				resource.ARN,
				resource.Owner,
				resource.SessionID,
			)
		}
	}
	claimTags := map[string]string{
		cpaws.TagOwner:   scope.Owner,
		cpaws.TagSession: scope.SessionID,
		cpaws.TagManaged: "true",
		// A destructive trusted-parent claim is already authorized for this
		// exact scope. Mark it expired now so a later broad run can recover if
		// this process stops after tag assurance but before deletion.
		cpaws.TagTTL: time.Now().UTC().Format(time.RFC3339),
	}
	for _, resource := range resources {
		verbose.Logf("AWS eks:TagResource trusted-parent arn=%s", resource.ARN)
		if _, err := client.TagResource(ctx, &eks.TagResourceInput{
			ResourceArn: awssdk.String(resource.ARN),
			Tags:        claimTags,
		}); err != nil {
			// The resource can be deleted between enumeration and tagging: a cluster
			// teardown removes its own nodegroups, and a concurrent sweep races this
			// one. A resource that is already gone needs no claim tag, so treat it as
			// satisfied rather than failing the whole account's cleanup over it.
			if destroyEKSNotFound(err) {
				verbose.Logf("trusted-parent EKS resource %s vanished before tagging; skipping: %v", resource.ARN, err)
				continue
			}
			return fmt.Errorf("tag trusted-parent EKS resource %s: %w", resource.ARN, err)
		}
	}
	return nil
}

func mergePurgeResources(base, added []cpaws.Resource) []cpaws.Resource {
	merged := append([]cpaws.Resource(nil), base...)
	seen := make(map[string]struct{}, len(merged))
	for _, resource := range merged {
		seen[resource.Region+"\x00"+resource.ARN] = struct{}{}
	}
	for _, resource := range added {
		key := resource.Region + "\x00" + resource.ARN
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		merged = append(merged, resource)
	}
	return merged
}

func planPurgeTrustedParentEKSClaims(
	ctx context.Context,
	scope cpaws.Scope,
	opCfg purgeOperatorConfig,
) ([]cpaws.Resource, []purgeTrustedParentClaim, error) {
	region := firstNonEmptyString(strings.TrimSpace(opCfg.Region), firstNonEmptyString(scope.Regions...))
	if region == "" {
		return nil, nil, fmt.Errorf("trusted-parent EKS dry-run planning requires region")
	}
	cfg, err := cmdLoadAWSConfig(ctx, opCfg.Profile, region)
	if err != nil {
		return nil, nil, fmt.Errorf("load EKS client for trusted-parent dry-run planning in %s: %w", region, err)
	}
	cfg.Region = region
	return discoverNukeEKSRoleChildren(
		ctx,
		purgeNewEKSRoleGraphClient(cfg),
		purgeNewEKSRoleProofClient(cfg),
		scope,
		opCfg.AccountID,
	)
}

func planNukeEKSRoleChildrenForExecution(
	ctx context.Context,
	scope cpaws.Scope,
	req *cpaws.NukeRunRequest,
) ([]cpaws.Resource, error) {
	if req == nil {
		return nil, fmt.Errorf("aws-nuke run request is required")
	}
	if req.AccessKeyID == "" || req.SecretAccessKey == "" {
		return nil, fmt.Errorf("aws-nuke credentials must be frozen before EKS trusted-parent preparation")
	}
	region := firstNonEmptyString(strings.TrimSpace(req.Region), firstNonEmptyString(scope.Regions...))
	if region == "" {
		return nil, fmt.Errorf("EKS trusted-parent preparation requires region")
	}
	cfg := destroyStaticAWSConfig(region, req.AccessKeyID, req.SecretAccessKey, req.SessionToken)
	resources, _, err := discoverNukeEKSRoleChildren(
		ctx,
		purgeNewEKSRoleGraphClient(cfg),
		purgeNewEKSRoleProofClient(cfg),
		scope,
		req.AccountID,
	)
	return resources, err
}

func tagPlannedNukeEKSRoleChildren(
	ctx context.Context,
	resources []cpaws.Resource,
	scope cpaws.Scope,
	req *cpaws.NukeRunRequest,
) error {
	if len(resources) == 0 {
		return nil
	}
	if req == nil {
		return fmt.Errorf("aws-nuke run request is required")
	}
	if req.AccessKeyID == "" || req.SecretAccessKey == "" {
		return fmt.Errorf("aws-nuke credentials must be frozen before EKS trusted-parent tagging")
	}
	region := firstNonEmptyString(strings.TrimSpace(req.Region), firstNonEmptyString(scope.Regions...))
	if region == "" {
		return fmt.Errorf("EKS trusted-parent tagging requires region")
	}
	cfg := destroyStaticAWSConfig(region, req.AccessKeyID, req.SecretAccessKey, req.SessionToken)
	return tagValidatedNukeEKSRoleChildren(ctx, purgeNewEKSRoleGraphClient(cfg), resources, scope)
}
