package aws

import (
	"fmt"
	"net"
	"net/url"
	"strings"
	"time"

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

const (
	awsCoverageIPAsset             = "ip_asset"
	awsCoverageEndpointOnly        = "endpoint_only"
	awsCoverageDNSEvidence         = "dns_evidence"
	awsCoverageUnsupportedExplicit = "unsupported_explicit"

	awsCoverageStatusSupported   = "supported"
	awsCoverageStatusUnsupported = "unsupported_explicit"
)

type awsPublicExposureCoverageEntry struct {
	Service       string `json:"service"`
	CoverageClass string `json:"coverage_class"`
	Status        string `json:"status"`
	Reason        string `json:"reason,omitempty"`
}

func awsPublicExposureCoverageRegistry() []awsPublicExposureCoverageEntry {
	return []awsPublicExposureCoverageEntry{
		{Service: "ec2", CoverageClass: awsCoverageIPAsset, Status: awsCoverageStatusSupported},
		{Service: "eni", CoverageClass: awsCoverageIPAsset, Status: awsCoverageStatusSupported},
		{Service: "eip", CoverageClass: awsCoverageIPAsset, Status: awsCoverageStatusSupported},
		{Service: "nat_gateway", CoverageClass: awsCoverageIPAsset, Status: awsCoverageStatusSupported},
		{Service: "vpc_endpoint_eni", CoverageClass: awsCoverageIPAsset, Status: awsCoverageStatusSupported},
		{Service: "elb_v2", CoverageClass: awsCoverageEndpointOnly, Status: awsCoverageStatusSupported},
		{Service: "nlb_static_ip", CoverageClass: awsCoverageIPAsset, Status: awsCoverageStatusSupported},
		{Service: "global_accelerator", CoverageClass: awsCoverageIPAsset, Status: awsCoverageStatusSupported},
		{Service: "lightsail_static_ip", CoverageClass: awsCoverageIPAsset, Status: awsCoverageStatusSupported},
		{Service: "classic_elb", CoverageClass: awsCoverageEndpointOnly, Status: awsCoverageStatusSupported},
		{Service: "cloudfront", CoverageClass: awsCoverageEndpointOnly, Status: awsCoverageStatusSupported},
		{Service: "s3", CoverageClass: awsCoverageEndpointOnly, Status: awsCoverageStatusSupported},
		{Service: "api_gateway", CoverageClass: awsCoverageEndpointOnly, Status: awsCoverageStatusSupported},
		{Service: "rds", CoverageClass: awsCoverageEndpointOnly, Status: awsCoverageStatusSupported},
		{Service: "redshift", CoverageClass: awsCoverageEndpointOnly, Status: awsCoverageStatusSupported},
		{Service: "eks", CoverageClass: awsCoverageEndpointOnly, Status: awsCoverageStatusSupported},
		{Service: "app_runner", CoverageClass: awsCoverageEndpointOnly, Status: awsCoverageStatusSupported},
		{Service: "opensearch", CoverageClass: awsCoverageEndpointOnly, Status: awsCoverageStatusSupported},
		{Service: "lambda_function_url", CoverageClass: awsCoverageEndpointOnly, Status: awsCoverageStatusSupported},
		{Service: "transfer_family", CoverageClass: awsCoverageEndpointOnly, Status: awsCoverageStatusSupported},
		{Service: "elastic_beanstalk", CoverageClass: awsCoverageEndpointOnly, Status: awsCoverageStatusSupported},
		{Service: "appsync", CoverageClass: awsCoverageEndpointOnly, Status: awsCoverageStatusSupported},
		{Service: "msk_public_access", CoverageClass: awsCoverageEndpointOnly, Status: awsCoverageStatusSupported},
		{Service: "cognito_hosted_domain", CoverageClass: awsCoverageEndpointOnly, Status: awsCoverageStatusSupported},
		{Service: "route53", CoverageClass: awsCoverageDNSEvidence, Status: awsCoverageStatusSupported, Reason: "Public DNS records are evidence only unless independently attributed to an AWS resource."},
		{Service: "waf", CoverageClass: awsCoverageUnsupportedExplicit, Status: awsCoverageStatusUnsupported, Reason: "Control-plane protection metadata only; no account-owned public endpoint or IP is emitted."},
		{Service: "shield", CoverageClass: awsCoverageUnsupportedExplicit, Status: awsCoverageStatusUnsupported, Reason: "Protection metadata only; no account-owned public endpoint or IP is emitted."},
	}
}

func awsPublicExposureCoverageMetadata() []map[string]interface{} {
	registry := awsPublicExposureCoverageRegistry()
	out := make([]map[string]interface{}, 0, len(registry))
	for _, entry := range registry {
		item := map[string]interface{}{
			"service":        entry.Service,
			"coverage_class": entry.CoverageClass,
			"status":         entry.Status,
		}
		if entry.Reason != "" {
			item["reason"] = entry.Reason
		}
		out = append(out, item)
	}
	return out
}

func awsCoverageChunkMetadata(service, coverageClass, status, reason string) map[string]interface{} {
	meta := map[string]interface{}{
		"service":        service,
		"coverage_class": coverageClass,
		"status":         status,
	}
	if reason != "" {
		meta["reason"] = reason
	}
	return meta
}

func appendAWSAttributedIPAsset(result *collector.AssetResult, assetNativeID, ipAddress, hostname string, isPublic bool, sourceService, resourceID string) {
	if result == nil || assetNativeID == "" || net.ParseIP(strings.TrimSpace(ipAddress)) == nil {
		return
	}
	ip := strings.TrimSpace(ipAddress)
	result.IPAssets = append(result.IPAssets, model.IPAsset{
		Asset:     model.Asset{NativeID: assetNativeID},
		IPAddress: ip,
		Hostname:  hostname,
		IsPublic:  isPublic,
		GeoLocation: coremodel.JSONB{
			"source_service":     sourceService,
			"resource_id":        resourceID,
			"owning_resource_id": resourceID,
			"ownership_status":   "attributed",
		},
	})
}

func inferAWSIPAssetSource(assetNativeID string) string {
	id := strings.ToLower(strings.TrimSpace(assetNativeID))
	switch {
	case strings.Contains(id, "globalaccelerator") || strings.Contains(id, "global_accelerator"):
		return "global_accelerator"
	case strings.Contains(id, "lightsail"):
		if strings.Contains(id, "staticip") || strings.Contains(id, "static-ip") || strings.Contains(id, "static_ip") {
			return "lightsail_static_ip"
		}
		return "lightsail"
	case strings.HasPrefix(id, "eipalloc-") || strings.HasPrefix(id, "eip:"):
		return "eip"
	case strings.HasPrefix(id, "nat-"):
		return "nat_gateway"
	case strings.HasPrefix(id, "vpce-"):
		return "vpc_endpoint_eni"
	case strings.HasPrefix(id, "eni-"):
		return "eni"
	case strings.HasPrefix(id, "i-"):
		return "ec2"
	case strings.Contains(id, ":loadbalancer/net/"):
		return "nlb_static_ip"
	case strings.Contains(id, ":loadbalancer/"):
		return "elb_v2"
	default:
		return "aws"
	}
}

func appendAWSServiceEndpoint(result *collector.AssetResult, assetNativeID, endpoint, service string, ports []uint32, evidence coremodel.JSONB) {
	if result == nil || assetNativeID == "" {
		return
	}
	host := normalizePublicEndpointHost(endpoint)
	if host == "" || net.ParseIP(host) != nil {
		return
	}
	observedAt := time.Now()
	domain := model.DomainAsset{
		Asset:        model.Asset{NativeID: assetNativeID, Service: service},
		DomainName:   host,
		DomainClass:  model.DomainClassServiceHostname,
		Registrar:    service,
		ZoneType:     "public",
		Status:       "active",
		LastSyncedAt: &observedAt,
		FirstSeenAt:  &observedAt,
		LastSeenAt:   &observedAt,
	}
	result.DomainAssets = append(result.DomainAssets, domain)
	for _, port := range ports {
		if port == 0 {
			continue
		}
		portEvidence := coremodel.JSONB{
			"source_service":   service,
			"resource_id":      assetNativeID,
			"evidence_source":  "service_endpoint",
			"ownership_status": "service_managed",
		}
		for k, v := range evidence {
			portEvidence[k] = v
		}
		result.Ports = append(result.Ports, model.IPPortIndex{
			Asset:             model.Asset{NativeID: assetNativeID},
			Endpoint:          host,
			Port:              port,
			Protocol:          "tcp",
			ObservationSource: model.PortObservationSourceServiceEndpoint,
			ServiceName:       getServiceName(port),
			Exposure:          "public",
			Status:            "open",
			Evidence:          portEvidence,
		})
	}
}

func appendAWSWebEndpoint(result *collector.AssetResult, assetNativeID, endpoint, service string, metadata coremodel.JSONB) {
	appendAWSServiceEndpoint(result, assetNativeID, endpoint, service, []uint32{443}, metadata)
}

func normalizePublicEndpointHost(endpoint string) string {
	value := strings.TrimSpace(endpoint)
	if value == "" {
		return ""
	}
	value = strings.TrimSuffix(value, ".")
	if parsed, err := url.Parse(value); err == nil && parsed.Host != "" {
		value = parsed.Host
	}
	value = strings.TrimPrefix(value, "http://")
	value = strings.TrimPrefix(value, "https://")
	if idx := strings.Index(value, "/"); idx >= 0 {
		value = value[:idx]
	}
	if host, _, err := net.SplitHostPort(value); err == nil {
		value = host
	}
	return strings.ToLower(strings.TrimSpace(value))
}

func awsPublicEndpointNativeID(service, region, id string) string {
	return fmt.Sprintf("%s:%s:%s", service, region, strings.TrimSpace(id))
}
