package vpc

import (
	"github.com/aquasecurity/defsec/provider/aws/vpc"
	"github.com/aquasecurity/defsec/types"
	"github.com/aquasecurity/tfsec/internal/pkg/block"
)

func Adapt(modules block.Modules) vpc.VPC {

	naclAdapter := naclAdapter{naclRuleIDs: modules.GetChildResourceIDMapByType("aws_network_acl_rule")}
	sgAdapter := sgAdapter{sgRuleIDs: modules.GetChildResourceIDMapByType("aws_security_group_rule")}

	rx := vpc.VPC{
		DefaultVPCs:    adaptDefaultVPCs(modules),
		SecurityGroups: sgAdapter.adaptSecurityGroups(modules),
		NetworkACLs:    naclAdapter.adaptNetworkACLs(modules),
	}
	return rx
}

type naclAdapter struct {
	naclRuleIDs block.ResourceIDResolutions
}

type sgAdapter struct {
	sgRuleIDs block.ResourceIDResolutions
}

func adaptDefaultVPCs(modules block.Modules) []vpc.DefaultVPC {
	var defaultVPCs []vpc.DefaultVPC
	for _, module := range modules {
		for _, resource := range module.GetResourcesByType("aws_default_vpc") {
			defaultVPCs = append(defaultVPCs, vpc.DefaultVPC{
				Metadata: resource.Metadata(),
			})
		}
	}
	return defaultVPCs
}

func (a *sgAdapter) adaptSecurityGroups(modules block.Modules) []vpc.SecurityGroup {
	var securityGroups []vpc.SecurityGroup
	for _, resource := range modules.GetResourcesByType("aws_security_group") {
		securityGroups = append(securityGroups, a.adaptSecurityGroup(resource, modules))
	}
	orphanResources := modules.GetResourceByIDs(a.sgRuleIDs.Orphans()...)
	if len(orphanResources) > 0 {
		orphanage := vpc.SecurityGroup{
			Metadata: types.NewUnmanagedMetadata(),
		}
		for _, sgRule := range orphanResources {
			if sgRule.GetAttribute("type").Equals("ingress") {
				orphanage.IngressRules = append(orphanage.IngressRules, adaptSGRule(sgRule, modules))
			} else if sgRule.GetAttribute("type").Equals("egress") {
				orphanage.EgressRules = append(orphanage.EgressRules, adaptSGRule(sgRule, modules))
			}
		}
		securityGroups = append(securityGroups, orphanage)
	}

	return securityGroups
}

func (a *naclAdapter) adaptNetworkACLs(modules block.Modules) []vpc.NetworkACL {
	var networkACLs []vpc.NetworkACL
	for _, module := range modules {
		for _, resource := range module.GetResourcesByType("aws_network_acl") {
			networkACLs = append(networkACLs, a.adaptNetworkACL(resource, module))
		}
	}

	orphanResources := modules.GetResourceByIDs(a.naclRuleIDs.Orphans()...)
	if len(orphanResources) > 0 {
		orphanage := vpc.NetworkACL{
			Metadata: types.NewUnmanagedMetadata(),
		}
		for _, naclRule := range orphanResources {
			orphanage.Rules = append(orphanage.Rules, adaptNetworkACLRule(naclRule))
		}
		networkACLs = append(networkACLs, orphanage)
	}

	return networkACLs
}

func (a *sgAdapter) adaptSecurityGroup(resource *block.Block, module block.Modules) vpc.SecurityGroup {
	var ingressRules []vpc.SecurityGroupRule
	var egressRules []vpc.SecurityGroupRule

	descriptionAttr := resource.GetAttribute("description")
	descriptionVal := descriptionAttr.AsStringValueOrDefault("Managed by Terraform", resource)

	ingressBlocks := resource.GetBlocks("ingress")
	for _, ingressBlock := range ingressBlocks {
		ingressRules = append(ingressRules, adaptSGRule(ingressBlock, module))
	}

	egressBlocks := resource.GetBlocks("egress")
	for _, egressBlock := range egressBlocks {
		egressRules = append(egressRules, adaptSGRule(egressBlock, module))
	}

	rulesBlocks := module.GetReferencingResources(resource, "aws_security_group_rule", "security_group_id")
	for _, ruleBlock := range rulesBlocks {
		a.sgRuleIDs.Resolve(ruleBlock.ID())
		if ruleBlock.GetAttribute("type").Equals("ingress") {
			ingressRules = append(ingressRules, adaptSGRule(ruleBlock, module))
		} else if ruleBlock.GetAttribute("type").Equals("egress") {
			egressRules = append(egressRules, adaptSGRule(ruleBlock, module))
		}
	}

	return vpc.SecurityGroup{
		Metadata:     resource.Metadata(),
		Description:  descriptionVal,
		IngressRules: ingressRules,
		EgressRules:  egressRules,
	}
}

func adaptSGRule(resource *block.Block, modules block.Modules) vpc.SecurityGroupRule {
	ruleDescAttr := resource.GetAttribute("description")
	ruleDescVal := ruleDescAttr.AsStringValueOrDefault("", resource)

	var cidrs []types.StringValue

	cidrBlocks := resource.GetAttribute("cidr_blocks")
	ipv6cidrBlocks := resource.GetAttribute("ipv6_cidr_blocks")
	varBlocks := modules.GetBlocks().OfType("variable")

	for _, vb := range varBlocks {
		if cidrBlocks.IsNotNil() && cidrBlocks.ReferencesBlock(vb) {
			cidrBlocks = vb.GetAttribute("default")
		}
		if ipv6cidrBlocks.IsNotNil() && ipv6cidrBlocks.ReferencesBlock(vb) {
			ipv6cidrBlocks = vb.GetAttribute("default")
		}
	}

	if cidrBlocks.IsNotNil() {
		cidrsList := cidrBlocks.ValueAsStrings()
		for _, cidr := range cidrsList {
			cidrs = append(cidrs, types.String(cidr, cidrBlocks.Metadata()))
		}
	} else {
		cidrs = append(cidrs, cidrBlocks.AsStringValueOrDefault("", resource))
	}

	if ipv6cidrBlocks.IsNotNil() {
		cidrsList := ipv6cidrBlocks.ValueAsStrings()
		for _, cidr := range cidrsList {
			cidrs = append(cidrs, types.String(cidr, ipv6cidrBlocks.Metadata()))
		}
	} else {
		cidrs = append(cidrs, ipv6cidrBlocks.AsStringValueOrDefault("", resource))
	}

	return vpc.SecurityGroupRule{
		Metadata:    resource.Metadata(),
		Description: ruleDescVal,
		CIDRs:       cidrs,
	}
}

func (a *naclAdapter) adaptNetworkACL(resource *block.Block, module *block.Module) vpc.NetworkACL {
	var networkRules []vpc.NetworkACLRule
	rulesBlocks := module.GetReferencingResources(resource, "aws_network_acl_rule", "network_acl_id")
	for _, ruleBlock := range rulesBlocks {
		a.naclRuleIDs.Resolve(ruleBlock.ID())
		networkRules = append(networkRules, adaptNetworkACLRule(ruleBlock))
	}
	return vpc.NetworkACL{
		Metadata: resource.Metadata(),
		Rules:    networkRules,
	}
}

func adaptNetworkACLRule(resource *block.Block) vpc.NetworkACLRule {
	var cidrs []types.StringValue

	typeVal := types.StringDefault("ingress", resource.Metadata())

	egressAtrr := resource.GetAttribute("egress")
	if egressAtrr.IsTrue() {
		typeVal = types.String("egress", resource.Metadata())
	}

	actionAttr := resource.GetAttribute("rule_action")
	actionVal := actionAttr.AsStringValueOrDefault("", resource)

	protocolAtrr := resource.GetAttribute("protocol")
	protocolVal := protocolAtrr.AsStringValueOrDefault("-1", resource)

	cidrAttr := resource.GetAttribute("cidr_block")
	ipv4cidrAttr := resource.GetAttribute("ipv6_cidr_block")
	cidrs = append(cidrs, cidrAttr.AsStringValueOrDefault("", resource))
	cidrs = append(cidrs, ipv4cidrAttr.AsStringValueOrDefault("", resource))

	return vpc.NetworkACLRule{
		Metadata: resource.Metadata(),
		Type:     typeVal,
		Action:   actionVal,
		Protocol: protocolVal,
		CIDRs:    cidrs,
	}
}
