package aws

import (
	"errors"
	"testing"
	"time"

	"github.com/aws/aws-sdk-go-v2/aws"
	route53types "github.com/aws/aws-sdk-go-v2/service/route53/types"

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

func TestBuildRoute53RecordBackedDomains(t *testing.T) {
	collectedAt := time.Date(2026, 3, 30, 12, 0, 0, 0, time.UTC)
	zone := caasmmodel.DomainAsset{
		Asset:       caasmmodel.Asset{NativeID: "zone-1"},
		DomainName:  "example.com",
		DomainClass: caasmmodel.DomainClassAuthoritativeZone,
		Registrar:   "route53",
		ZoneID:      "Z123",
		ZoneType:    "public",
		Status:      "active",
	}
	records := coremodel.JSONB{
		"records": []map[string]interface{}{
			{"name": "example.com.", "type": "A", "values": []string{"1.1.1.1"}},
			{"name": "api.example.com.", "type": "CNAME", "values": []string{"dualstack.elb.amazonaws.com."}},
			{"name": "api.example.com.", "type": "TXT", "values": []string{"v=spf1"}},
		},
		"fetched": collectedAt,
	}

	derived := buildRoute53RecordBackedDomains(zone, records, collectedAt)
	if len(derived) != 2 {
		t.Fatalf("buildRoute53RecordBackedDomains() len = %d, want 2", len(derived))
	}

	if derived[0].DomainClass != caasmmodel.DomainClassDNSRecord || derived[1].DomainClass != caasmmodel.DomainClassDNSRecord {
		t.Fatalf("expected all derived domains to be dns_record class")
	}
	if derived[0].DomainName != "api.example.com" || derived[1].DomainName != "example.com" {
		t.Fatalf("unexpected derived domain names: %#v %#v", derived[0].DomainName, derived[1].DomainName)
	}

	apiRecords, _ := derived[0].DNSRecords["records"].([]map[string]interface{})
	if len(apiRecords) != 2 {
		t.Fatalf("expected api.example.com to retain grouped records, got %d", len(apiRecords))
	}
}

func TestNormalizeRoute53RecordSetRestoresWildcardLabel(t *testing.T) {
	tests := []struct {
		name  string
		input string
	}{
		{name: "single escaped wildcard", input: "\\052.example.com."},
		{name: "double escaped wildcard", input: "\\\\052.example.com."},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			rr := route53types.ResourceRecordSet{
				Name: aws.String(tt.input),
				Type: route53types.RRTypeA,
			}

			record, name := normalizeRoute53RecordSet(rr)
			if name != "*.example.com" {
				t.Fatalf("normalizeRoute53RecordSet() name = %q, want %q", name, "*.example.com")
			}

			gotName, _ := record["name"].(string)
			if gotName != "*.example.com" {
				t.Fatalf("record[name] = %q, want %q", gotName, "*.example.com")
			}
		})
	}
}

func TestBuildRoute53RecordBackedDomainsNormalizesWildcardNames(t *testing.T) {
	collectedAt := time.Date(2026, 3, 30, 12, 0, 0, 0, time.UTC)
	zone := caasmmodel.DomainAsset{
		Asset:       caasmmodel.Asset{NativeID: "zone-1"},
		DomainName:  "example.com",
		DomainClass: caasmmodel.DomainClassAuthoritativeZone,
	}
	records := coremodel.JSONB{
		"records": []map[string]interface{}{
			{"name": "\\052.example.com.", "type": "A", "values": []string{"1.1.1.1"}},
		},
		"fetched": collectedAt,
	}

	derived := buildRoute53RecordBackedDomains(zone, records, collectedAt)
	if len(derived) != 1 {
		t.Fatalf("buildRoute53RecordBackedDomains() len = %d, want 1", len(derived))
	}
	if derived[0].DomainName != "*.example.com" {
		t.Fatalf("derived domain = %q, want %q", derived[0].DomainName, "*.example.com")
	}
}

func TestNormalizeRoute53RecordSetNormalizesAliasTargetAndCNAMEValues(t *testing.T) {
	rr := route53types.ResourceRecordSet{
		Name: aws.String("service.example.com."),
		Type: route53types.RRTypeCname,
		AliasTarget: &route53types.AliasTarget{
			DNSName:              aws.String("\\052.target.example.com."),
			EvaluateTargetHealth: true,
		},
		ResourceRecords: []route53types.ResourceRecord{
			{Value: aws.String("\\052.origin.example.com.")},
		},
	}

	record, _ := normalizeRoute53RecordSet(rr)
	alias, _ := record["alias_target"].(map[string]interface{})
	if got, _ := alias["dns_name"].(string); got != "*.target.example.com" {
		t.Fatalf("alias_target.dns_name = %q, want %q", got, "*.target.example.com")
	}

	values, _ := record["values"].([]string)
	if len(values) != 1 || values[0] != "*.origin.example.com" {
		t.Fatalf("values = %#v, want [*.origin.example.com]", values)
	}
}

func TestNormalizeRoute53RecordValueNormalizesMXAndSRVTargets(t *testing.T) {
	mx := normalizeRoute53RecordValue(route53types.RRTypeMx, "10 \\052.mail.example.com.")
	if mx != "10 *.mail.example.com" {
		t.Fatalf("mx = %q, want %q", mx, "10 *.mail.example.com")
	}

	srv := normalizeRoute53RecordValue(route53types.RRTypeSrv, "1 5 443 \\052.svc.example.com.")
	if srv != "1 5 443 *.svc.example.com" {
		t.Fatalf("srv = %q, want %q", srv, "1 5 443 *.svc.example.com")
	}
}

func TestBuildResolverLevelDNSHealth(t *testing.T) {
	t.Run("labels_resolver_scope", func(t *testing.T) {
		health := buildResolverLevelDNSHealth([]string{"ns-1.example.net"}, 25*time.Millisecond, nil)
		if got := health["check_scope"]; got != "resolver" {
			t.Fatalf("check_scope = %v, want resolver", got)
		}
		if got := health["check_method"]; got != "system_resolver_lookup" {
			t.Fatalf("check_method = %v, want system_resolver_lookup", got)
		}
		if got := health["status"]; got != "healthy" {
			t.Fatalf("status = %v, want healthy", got)
		}
	})

	t.Run("marks_missing_nameservers_unknown", func(t *testing.T) {
		health := buildResolverLevelDNSHealth(nil, 0, errors.New("no_nameservers"))
		if got := health["status"]; got != "unknown" {
			t.Fatalf("status = %v, want unknown", got)
		}
		if got := health["reason"]; got != "no_nameservers" {
			t.Fatalf("reason = %v, want no_nameservers", got)
		}
	})

	t.Run("marks_lookup_errors_unhealthy", func(t *testing.T) {
		health := buildResolverLevelDNSHealth([]string{"ns-1.example.net"}, 10*time.Millisecond, errors.New("lookup failed"))
		if got := health["status"]; got != "unhealthy" {
			t.Fatalf("status = %v, want unhealthy", got)
		}
	})
}
