data "aws_region" "current" {}

locals {
  availability_zones = setunion(
    [for subnet in var.vpc_private_subnets : subnet["availability_zone"]],
    [for subnet in var.vpc_public_subnets : subnet["availability_zone"]]
  )

  additional_cidr_blocks_map = { for idx, cidr in var.additional_cidr_blocks : idx => cidr }

  private_prefix_list_entries = {
    for subnet in var.vpc_private_subnets : subnet["cidr_block"] => {
      cidr_block        = subnet["cidr_block"]
      availability_zone = subnet["availability_zone"]
    }
  }

  public_prefix_list_entries = {
    for subnet in var.vpc_public_subnets : subnet["cidr_block"] => {
      cidr_block        = subnet["cidr_block"]
      availability_zone = subnet["availability_zone"]
    }
  }
}

resource "aws_vpc" "default" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true

  tags = {
    Name        = coalesce(var.custom_vpc_name, "${var.env}-terraform-aws-vpc")
    terraformed = true
  }
}

# add public subnets
resource "aws_subnet" "public_default" {
  for_each = {
    for subnet in var.vpc_public_subnets :
    subnet["cidr_block"] => subnet
  }

  vpc_id            = aws_vpc.default.id
  cidr_block        = each.value["cidr_block"]
  availability_zone = each.value["availability_zone"]

  # Ensure secondary CIDR association exists if needed
  depends_on = [aws_vpc_ipv4_cidr_block_association.additional]

  tags = {
    Name        = each.value["name"]
    subnet_type = "public"
    tier        = "public"
  }
}

resource "aws_ec2_managed_prefix_list" "public" {
  address_family = "IPv4"
  max_entries    = length(local.public_prefix_list_entries)
  name           = coalesce(var.custom_public_subnet_prefix_list_name, "${var.env}-${var.account_group}-public-subnet-prefix-list")

  dynamic "entry" {
    for_each = local.public_prefix_list_entries
    content {
      cidr        = entry.key
      description = "${var.env} ${var.account_group} public subnet ${entry.value["cidr_block"]} in ${entry.value["availability_zone"]}"
    }
  }

  tags = {
    account_group = var.account_group
    environment   = var.env
    service_name  = "managed-prefix-list"
    subnet_group  = "all"
    subnet_type   = "public"
    tier          = "public"
    terraformed   = true
    vpc_id        = aws_vpc.default.id
  }
}

resource "aws_ram_resource_share" "public_subnet" {
  name = "${var.env}-${var.account_group}-public-subnet-managed-prefix-list-share"

  tags = {
    account_group = var.account_group
    environment   = var.env
    service_name  = "managed-prefix-list"
    terraformed   = true
  }
}

resource "aws_ram_resource_association" "public_subnet_association" {
  resource_arn       = aws_ec2_managed_prefix_list.public.arn
  resource_share_arn = aws_ram_resource_share.public_subnet.arn
}

resource "aws_ram_principal_association" "public_subnet_principal_association" {
  for_each = var.resource_share_principals

  principal          = each.value
  resource_share_arn = aws_ram_resource_share.public_subnet.arn
}

# add route table
resource "aws_route_table" "public_default" {
  vpc_id = aws_vpc.default.id

  tags = {
    Name = "${var.env}_public_subnet_route"
  }
}

# associate route table with public subnets
resource "aws_route_table_association" "public_default" {
  for_each = {
    for subnet in var.vpc_public_subnets :
    subnet["cidr_block"] => subnet
  }

  subnet_id      = aws_subnet.public_default[each.key].id
  route_table_id = aws_route_table.public_default.id
}

# add internet gateway
resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.default.id

  tags = {
    Name = "${var.env}_igw"
  }
}

# add a public gateway to public route table
resource "aws_route" "public_gateway_route" {
  route_table_id         = aws_route_table.public_default.id
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = aws_internet_gateway.igw.id
}

# add public network acl
resource "aws_network_acl" "public_default_nacl" {
  vpc_id     = aws_vpc.default.id
  subnet_ids = [for subnet in values(aws_subnet.public_default) : subnet.id]
  egress {
    protocol   = "-1"
    rule_no    = 2
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 0
    to_port    = 0
  }
  ingress {
    protocol   = "-1"
    rule_no    = 1
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 0
    to_port    = 0
  }

  tags = {
    Name = "${var.env}_public_network_ACL"
  }
}

# add private subnets
resource "aws_subnet" "private_default" {
  for_each = {
    for subnet in var.vpc_private_subnets :
    subnet["cidr_block"] => subnet
  }

  vpc_id            = aws_vpc.default.id
  cidr_block        = each.value["cidr_block"]
  availability_zone = each.value["availability_zone"]

  # Ensure secondary CIDR association exists if needed
  depends_on = [aws_vpc_ipv4_cidr_block_association.additional]

  tags = {
    Name        = each.value["name"]
    subnet_type = "private"
    tier        = "private"
  }
}

resource "aws_ec2_managed_prefix_list" "private" {
  address_family = "IPv4"
  max_entries    = length(local.private_prefix_list_entries)
  name           = coalesce(var.custom_private_subnet_prefix_list_name, "${var.env}-${var.account_group}-private-subnet-prefix-list")

  dynamic "entry" {
    for_each = local.private_prefix_list_entries
    content {
      cidr        = entry.key
      description = "${var.env} ${var.account_group} private subnet ${entry.value["cidr_block"]} in ${entry.value["availability_zone"]}"
    }
  }

  tags = {
    account_group = var.account_group
    environment   = var.env
    service_name  = "managed-prefix-list"
    subnet_group  = "all"
    subnet_type   = "private"
    tier          = "private"
    terraformed   = true
    vpc_id        = aws_vpc.default.id
  }
}

resource "aws_ram_resource_share" "private_subnet" {
  name = "${var.env}-${var.account_group}-private-subnet-managed-prefix-list-share"

  tags = {
    account_group = var.account_group
    environment   = var.env
    service_name  = "managed-prefix-list"
    terraformed   = true
  }
}

resource "aws_ram_resource_association" "private_subnet_association" {
  resource_arn       = aws_ec2_managed_prefix_list.private.arn
  resource_share_arn = aws_ram_resource_share.private_subnet.arn
}

resource "aws_ram_principal_association" "private_subnet_principal_association" {
  for_each = var.resource_share_principals

  principal          = each.value
  resource_share_arn = aws_ram_resource_share.private_subnet.arn
}


# add route table
resource "aws_route_table" "private_default" {
  for_each = local.availability_zones

  vpc_id = aws_vpc.default.id

  tags = {
    Name = "${var.env}_private_route_table_${each.value}"
  }
}

# associate route table with private subnets
resource "aws_route_table_association" "private_default" {
  for_each = {
    for subnet in var.vpc_private_subnets :
    subnet["cidr_block"] => subnet
  }

  subnet_id      = aws_subnet.private_default[each.key].id
  route_table_id = aws_route_table.private_default[each.value["availability_zone"]].id
}

resource "aws_eip" "nat_eip" {
  for_each = local.availability_zones

  domain = "vpc"
}

resource "aws_nat_gateway" "nat_gw" {
  for_each = local.availability_zones

  allocation_id = aws_eip.nat_eip[each.value].id
  subnet_id     = [for subnet in aws_subnet.public_default : subnet if subnet.availability_zone == each.value][0].id

  depends_on = [aws_internet_gateway.igw]

  tags = {
    Name = "Nat_Gateway_${each.value}"
  }
}

# add a nat gateway to private route table
resource "aws_route" "private_nat_gateway_route" {
  for_each = local.availability_zones

  route_table_id         = aws_route_table.private_default[each.value].id
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id         = aws_nat_gateway.nat_gw[each.value].id
}

# add private network acl
resource "aws_network_acl" "private_default_nacl" {
  vpc_id     = aws_vpc.default.id
  subnet_ids = [for subnet in values(aws_subnet.private_default) : subnet.id]
  egress {
    protocol   = "-1"
    rule_no    = 2
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 0
    to_port    = 0
  }
  ingress {
    protocol   = "-1"
    rule_no    = 1
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 0
    to_port    = 0
  }

  tags = {
    Name = "${var.env}_private_network_ACL"
  }
}

# Remove all rules from the default VPC security group.
resource "aws_default_security_group" "default" {
  vpc_id = aws_vpc.default.id

  tags = {
    "eiso-exception" = "aws.08.30"
  }
}

# Configure gateway endpoints to save on data transfer costs within the same region.
resource "aws_vpc_endpoint" "s3" {
  service_name = "com.amazonaws.${data.aws_region.current.id}.s3"
  vpc_id       = aws_vpc.default.id
  route_table_ids = concat(
    [aws_route_table.public_default.id],
    [for route_table in values(aws_route_table.private_default) : route_table.id]
  )

  tags = {
    Name         = "${var.env}-s3-vpc-endpoint"
    service_name = "vpc-endpoint"
    terraformed  = true
  }
}

resource "aws_vpc_endpoint" "dynamodb" {
  service_name = "com.amazonaws.${data.aws_region.current.id}.dynamodb"
  vpc_id       = aws_vpc.default.id
  route_table_ids = concat(
    [aws_route_table.public_default.id],
    [for route_table in values(aws_route_table.private_default) : route_table.id]
  )

  tags = {
    Name         = "${var.env}-dynamodb-vpc-endpoint"
    service_name = "vpc-endpoint"
    terraformed  = true
  }
}

resource "aws_flow_log" "flow_log" {
  for_each = var.vpc_flow_logs

  vpc_id               = aws_vpc.default.id
  iam_role_arn         = lookup(each.value, "iam_role_arn", null)
  log_destination      = each.value["log_destination"]
  log_destination_type = each.value["log_destination_type"]
  traffic_type         = each.value["traffic_type"]

  tags = {
    Name         = "${var.env}-${each.key}"
    service_name = "vpc-flow-log"
    terraformed  = true
  }
}

resource "aws_elasticache_subnet_group" "default_elasticache_subnet_group" {
  name       = coalesce(var.custom_elasticache_subnet_group_name, "${var.env}-elasticache-subnet-group")
  subnet_ids = [for subnet in values(aws_subnet.private_default) : subnet.id]
}

data "aws_route53_resolver_firewall_rule_group" "dns_firewall_rule_group" {
  for_each               = toset(var.dns_firewall_rule_group_id == "" ? [] : ["group"])
  firewall_rule_group_id = var.dns_firewall_rule_group_id
}

resource "aws_route53_resolver_firewall_rule_group_association" "vpc_association" {
  for_each               = toset(var.dns_firewall_rule_group_id == "" ? [] : ["group"])
  name                   = "${aws_vpc.default.id}-dns-firewall-rule-group-association"
  firewall_rule_group_id = data.aws_route53_resolver_firewall_rule_group.dns_firewall_rule_group["group"].id
  priority               = var.dns_firewall_rule_group_priority
  vpc_id                 = aws_vpc.default.id
}

# Additional VPC CIDR blocks
resource "aws_vpc_ipv4_cidr_block_association" "additional" {
  for_each = local.additional_cidr_blocks_map

  vpc_id     = aws_vpc.default.id
  cidr_block = each.value["cidr_block"]
}
