package cmd

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

	awssdk "github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/service/dynamodb"
	"github.com/aws/aws-sdk-go-v2/service/ecr"
	"github.com/aws/aws-sdk-go-v2/service/lambda"
	"github.com/aws/aws-sdk-go-v2/service/sqs"

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

// The purge deletes these serverless resource types directly, precisely scoped
// to the session's survivors. In the shared cleanup account aws-nuke deliberately
// does not target them (see TestDestroyNukeResourceTypesSkipUnsupportedShared
// AccountTypes); a real `cp9 destroy` relies on the in-cluster operator to remove
// them during workload teardown, but the aws-nuke-only purge has no cluster, so
// starter-dataplane DynamoDB tables, Lambda functions/event-source-mappings, SQS
// queues, and ECR repositories otherwise survive every run (and Lambda-in-VPC
// ENIs block the VPC teardown). Mirrors the direct-cleanup pattern used for
// S3/IAM/Secrets/launch-templates.

type purgeDynamoDBDeleteAPI interface {
	DeleteTable(ctx context.Context, params *dynamodb.DeleteTableInput, optFns ...func(*dynamodb.Options)) (*dynamodb.DeleteTableOutput, error)
}

type purgeLambdaDeleteAPI interface {
	DeleteFunction(ctx context.Context, params *lambda.DeleteFunctionInput, optFns ...func(*lambda.Options)) (*lambda.DeleteFunctionOutput, error)
	DeleteEventSourceMapping(
		ctx context.Context,
		params *lambda.DeleteEventSourceMappingInput,
		optFns ...func(*lambda.Options),
	) (*lambda.DeleteEventSourceMappingOutput, error)
}

type purgeSQSDeleteAPI interface {
	DeleteQueue(ctx context.Context, params *sqs.DeleteQueueInput, optFns ...func(*sqs.Options)) (*sqs.DeleteQueueOutput, error)
}

type purgeECRDeleteAPI interface {
	DeleteRepository(ctx context.Context, params *ecr.DeleteRepositoryInput, optFns ...func(*ecr.Options)) (*ecr.DeleteRepositoryOutput, error)
}

var (
	purgeNewDynamoDBClient = func(cfg awssdk.Config) purgeDynamoDBDeleteAPI { return dynamodb.NewFromConfig(cfg) }
	purgeNewLambdaClient   = func(cfg awssdk.Config) purgeLambdaDeleteAPI { return lambda.NewFromConfig(cfg) }
	purgeNewSQSClient      = func(cfg awssdk.Config) purgeSQSDeleteAPI { return sqs.NewFromConfig(cfg) }
	purgeNewECRClient      = func(cfg awssdk.Config) purgeECRDeleteAPI { return ecr.NewFromConfig(cfg) }
)

type purgeServerlessClients struct {
	dynamodb purgeDynamoDBDeleteAPI
	lambda   purgeLambdaDeleteAPI
	sqs      purgeSQSDeleteAPI
	ecr      purgeECRDeleteAPI
}

// purgeDeleteServerlessResources deletes the DynamoDB / Lambda / SQS / ECR
// survivors directly. The survivors are already owner+session scoped by the
// reaper. Best-effort and idempotent: an already-gone resource is a success, and
// other per-resource errors are logged so the post-nuke verify stays the arbiter.
func purgeDeleteServerlessResources(ctx context.Context, resources []cpaws.Resource, req *cpaws.NukeRunRequest) ([]string, error) {
	if req == nil || req.AccessKeyID == "" || req.SecretAccessKey == "" {
		return nil, fmt.Errorf("aws-nuke credentials must be frozen before serverless cleanup")
	}
	clients := map[string]*purgeServerlessClients{}
	clientsFor := func(region string) *purgeServerlessClients {
		if c := clients[region]; c != nil {
			return c
		}
		cfg := destroyStaticAWSConfig(region, req.AccessKeyID, req.SecretAccessKey, req.SessionToken)
		c := &purgeServerlessClients{
			dynamodb: purgeNewDynamoDBClient(cfg),
			lambda:   purgeNewLambdaClient(cfg),
			sqs:      purgeNewSQSClient(cfg),
			ecr:      purgeNewECRClient(cfg),
		}
		clients[region] = c
		return c
	}

	var deleted []string
	for i := range resources {
		r := &resources[i]
		arn := strings.TrimSpace(r.ARN)
		if arn == "" {
			continue
		}
		region := strings.TrimSpace(r.Region)
		if region == "" {
			region = cpaws.ParseResourceRegion(arn)
		}
		if region == "" {
			region = strings.TrimSpace(req.Region)
		}
		if region == "" {
			continue
		}
		c := clientsFor(region)

		var err error
		switch r.Type {
		case cpaws.ResourceDynamoDB:
			if name := arnResourceSuffix(arn, "table/"); name != "" {
				_, err = c.dynamodb.DeleteTable(ctx, &dynamodb.DeleteTableInput{TableName: awssdk.String(name)})
			}
		case cpaws.ResourceLambda:
			if uuid := arnResourceSuffix(arn, "event-source-mapping:"); uuid != "" {
				_, err = c.lambda.DeleteEventSourceMapping(ctx, &lambda.DeleteEventSourceMappingInput{UUID: awssdk.String(uuid)})
			} else {
				_, err = c.lambda.DeleteFunction(ctx, &lambda.DeleteFunctionInput{FunctionName: awssdk.String(arn)})
			}
		case cpaws.ResourceSQS:
			if url := sqsQueueURLFromARN(arn); url != "" {
				_, err = c.sqs.DeleteQueue(ctx, &sqs.DeleteQueueInput{QueueUrl: awssdk.String(url)})
			}
		case cpaws.ResourceECR:
			if name := arnResourceSuffix(arn, "repository/"); name != "" {
				_, err = c.ecr.DeleteRepository(ctx, &ecr.DeleteRepositoryInput{RepositoryName: awssdk.String(name), Force: true})
			}
		default:
			continue
		}
		if err != nil {
			if purgeResourceAlreadyGone(err) {
				deleted = append(deleted, arn)
				continue
			}
			fmt.Fprintf(os.Stderr, "purge: delete serverless resource %s (%s): %v\n", arn, r.Type, err)
			continue
		}
		deleted = append(deleted, arn)
	}
	return deleted, nil
}

func arnResourcePart(arn string) string {
	parts := strings.SplitN(arn, ":", 6)
	if len(parts) < 6 {
		return ""
	}
	return parts[5]
}

func arnResourceSuffix(arn, prefix string) string {
	res := arnResourcePart(arn)
	if after, ok := strings.CutPrefix(res, prefix); ok {
		return after
	}
	return ""
}

// sqsQueueURLFromARN builds the queue URL DeleteQueue needs from an SQS ARN
// (arn:aws:sqs:region:account:queue-name).
func sqsQueueURLFromARN(arn string) string {
	parts := strings.SplitN(arn, ":", 6)
	if len(parts) < 6 {
		return ""
	}
	region, account, name := parts[3], parts[4], parts[5]
	if region == "" || account == "" || name == "" {
		return ""
	}
	return fmt.Sprintf("https://sqs.%s.amazonaws.com/%s/%s", region, account, name)
}

func purgeResourceAlreadyGone(err error) bool {
	if err == nil {
		return false
	}
	msg := err.Error()
	for _, marker := range []string{
		"NotFound",
		"ResourceNotFoundException",
		"RepositoryNotFoundException",
		"NonExistentQueue",
		"does not exist",
	} {
		if strings.Contains(msg, marker) {
			return true
		}
	}
	return false
}
