package aws

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

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing"
	classictypes "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing/types"

	coremodel "security-center-backend/internal/model"
	"security-center-backend/internal/modules/caasm/collector"
	model "security-center-backend/internal/modules/caasm/model"
)

func (c *AWSCollector) collectClassicELB(ctx context.Context, region string, result *collector.AssetResult) error {
	svc := elasticloadbalancing.NewFromConfig(c.cfg, func(o *elasticloadbalancing.Options) {
		o.Region = region
	})
	paginator := elasticloadbalancing.NewDescribeLoadBalancersPaginator(svc, &elasticloadbalancing.DescribeLoadBalancersInput{})
	observedAt := time.Now().UTC()

	for paginator.HasMorePages() {
		page, err := paginator.NextPage(ctx)
		if err != nil {
			return err
		}
		for _, lb := range page.LoadBalancerDescriptions {
			if err := c.checkExecutionGate(ctx); err != nil {
				return err
			}
			c.appendClassicELB(region, observedAt, lb, result)
		}
	}
	return nil
}

func (c *AWSCollector) appendClassicELB(region string, observedAt time.Time, lb classictypes.LoadBalancerDescription, result *collector.AssetResult) {
	name := aws.ToString(lb.LoadBalancerName)
	if name == "" {
		return
	}
	nativeID := fmt.Sprintf("classic-elb:%s:%s", region, name)
	status := "active"
	scheme := strings.ToLower(aws.ToString(lb.Scheme))
	isPublic := scheme == "" || scheme == "internet-facing"
	exposure := "private"
	if isPublic {
		exposure = "public"
	}
	asset := c.newBaseAsset(nativeID, name, string(model.AssetTypeNetwork), status, region)
	c.setService(&asset, "classic_elb")
	asset.Metadata = coremodel.JSONB{
		"dns_name":                      lb.DNSName,
		"canonical_hosted_zone_name":    lb.CanonicalHostedZoneName,
		"canonical_hosted_zone_name_id": lb.CanonicalHostedZoneNameID,
		"scheme":                        lb.Scheme,
		"availability_zones":            lb.AvailabilityZones,
		"subnets":                       lb.Subnets,
		"security_groups":               lb.SecurityGroups,
		"vpc_id":                        lb.VPCId,
		"listeners":                     classicELBListenerMetadata(lb.ListenerDescriptions),
		"is_public":                     isPublic,
	}
	result.Assets = append(result.Assets, asset)

	dnsName := aws.ToString(lb.DNSName)
	if dnsName == "" {
		return
	}
	result.DomainAssets = append(result.DomainAssets, directPublicDomainAsset(asset.NativeID, dnsName, observedAt))
	for _, listenerDescription := range lb.ListenerDescriptions {
		if listenerDescription.Listener == nil {
			continue
		}
		listener := listenerDescription.Listener
		if listener.LoadBalancerPort < 0 || listener.LoadBalancerPort > 65535 {
			continue
		}
		port := uint32(listener.LoadBalancerPort)
		protocol := strings.ToLower(aws.ToString(listener.Protocol))
		if protocol == "http" || protocol == "https" || protocol == "ssl" {
			protocol = "tcp"
		}
		record := directPublicEndpointPort(asset.NativeID, dnsName, port, protocol, model.PortObservationSourceListener, exposure, getServiceName(port), directPublicPortEvidence("classic_elb", asset.NativeID, "classic_elb_listener", "account_resource"))
		record.Confidence = directPublicConfidence("low", "Classic ELB listener on service-managed DNS endpoint")
		result.Ports = append(result.Ports, record)
	}
}

func classicELBListenerMetadata(listeners []classictypes.ListenerDescription) []map[string]interface{} {
	out := make([]map[string]interface{}, 0, len(listeners))
	for _, description := range listeners {
		if description.Listener == nil {
			continue
		}
		listener := description.Listener
		out = append(out, map[string]interface{}{
			"load_balancer_port": listener.LoadBalancerPort,
			"protocol":           listener.Protocol,
			"instance_port":      listener.InstancePort,
			"instance_protocol":  listener.InstanceProtocol,
			"policies":           description.PolicyNames,
		})
	}
	return out
}
