terraform {
  required_version = ">= 1.7"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.23"
    }
  }
}

locals {
  secondary_eip_count = 7
  nat_gateway_count   = var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : length(var.azs)) : 0
  nat_secondary_eips = var.enable_nat_gateway ? {
    for pair in flatten([
      for nat_idx in range(local.nat_gateway_count) : [
        for sec_idx in range(local.secondary_eip_count) : {
          key     = "${var.azs[nat_idx]}-${sec_idx}"
          nat_idx = nat_idx
        }
      ]
    ]) : pair.key => pair
  } : {}
}

# VPC
resource "aws_vpc" "this" {
  cidr_block                       = var.cidr
  assign_generated_ipv6_cidr_block = true
  enable_dns_hostnames             = var.enable_dns_hostnames
  enable_dns_support               = var.enable_dns_support

  tags = merge(
    var.tags,
    {
      Name = var.name
    }
  )
}

# Internet Gateway
resource "aws_internet_gateway" "this" {
  vpc_id = aws_vpc.this.id

  tags = merge(
    var.tags,
    {
      Name = var.name
    }
  )
}

# Egress-Only Internet Gateway (IPv6 outbound from private subnets)
resource "aws_egress_only_internet_gateway" "this" {
  vpc_id = aws_vpc.this.id

  tags = merge(
    var.tags,
    {
      Name = var.name
    }
  )
}

# Public Subnets
resource "aws_subnet" "public" {
  count = length(var.public_subnets)

  vpc_id                          = aws_vpc.this.id
  cidr_block                      = var.public_subnets[count.index]
  ipv6_cidr_block                 = cidrsubnet(aws_vpc.this.ipv6_cidr_block, 8, count.index + 100)
  assign_ipv6_address_on_creation = true
  availability_zone               = var.azs[count.index]
  map_public_ip_on_launch         = true

  tags = merge(
    var.tags,
    {
      Name                     = "${var.name}-public-${var.azs[count.index]}"
      "kubernetes.io/role/elb" = "1"
    }
  )

  # Force sequential creation to avoid AWS eventual consistency issues
  depends_on = [aws_vpc.this, aws_internet_gateway.this]
}

# Private Subnets
resource "aws_subnet" "private" {
  count = length(var.private_subnets)

  vpc_id                          = aws_vpc.this.id
  cidr_block                      = var.private_subnets[count.index]
  ipv6_cidr_block                 = cidrsubnet(aws_vpc.this.ipv6_cidr_block, 8, count.index)
  assign_ipv6_address_on_creation = true
  availability_zone               = var.azs[count.index]

  tags = merge(
    var.tags,
    var.private_subnet_tags,
    {
      Name                              = "${var.name}-private-${var.azs[count.index]}"
      "kubernetes.io/role/internal-elb" = "1"
    }
  )

  lifecycle {
    ignore_changes = [
      tags["karpenter.sh/discovery"],
    ]
  }

  # Force sequential creation to avoid AWS eventual consistency issues
  depends_on = [aws_vpc.this, aws_internet_gateway.this]
}

# Elastic IPs for NAT Gateways
resource "aws_eip" "nat" {
  count  = var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : length(var.azs)) : 0
  domain = "vpc"

  tags = merge(
    var.tags,
    {
      Name = "${var.name}-nat-${count.index + 1}"
    }
  )

  depends_on = [aws_internet_gateway.this]
}

# Secondary Elastic IPs for NAT Gateways (allows up to 8 total EIPs per NAT GW)
resource "aws_eip" "nat_secondary" {
  for_each = local.nat_secondary_eips
  domain   = "vpc"
  tags = merge(
    var.tags,
    {
      Name = "${var.name}-nat-${each.value.nat_idx + 1}-sec-${each.key}"
    }
  )
  depends_on = [aws_internet_gateway.this]
}

# NAT Gateways
resource "aws_nat_gateway" "this" {
  count = var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : length(var.azs)) : 0

  allocation_id = aws_eip.nat[count.index].id
  secondary_allocation_ids = [
    for sec in local.nat_secondary_eips : aws_eip.nat_secondary[sec.key].id
    if sec.nat_idx == count.index
  ]
  subnet_id = aws_subnet.public[count.index].id

  tags = merge(
    var.tags,
    {
      Name = "${var.name}-nat-${count.index + 1}"
    }
  )

  depends_on = [aws_internet_gateway.this]
}

# Public Route Table
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.this.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.this.id
  }

  route {
    ipv6_cidr_block = "::/0"
    gateway_id      = aws_internet_gateway.this.id
  }

  tags = merge(
    var.tags,
    {
      Name = "${var.name}-public"
    }
  )
}

resource "aws_route_table_association" "public" {
  count = length(var.public_subnets)

  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}

# Private Route Tables
resource "aws_route_table" "private" {
  count = var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : length(var.azs)) : length(var.azs)

  vpc_id = aws_vpc.this.id

  dynamic "route" {
    for_each = var.enable_nat_gateway ? [1] : []
    content {
      cidr_block     = "0.0.0.0/0"
      nat_gateway_id = var.single_nat_gateway ? aws_nat_gateway.this[0].id : aws_nat_gateway.this[count.index].id
    }
  }

  route {
    ipv6_cidr_block        = "::/0"
    egress_only_gateway_id = aws_egress_only_internet_gateway.this.id
  }

  tags = merge(
    var.tags,
    {
      Name = "${var.name}-private-${count.index + 1}"
    }
  )
}

resource "aws_route_table_association" "private" {
  count = length(var.private_subnets)

  subnet_id      = aws_subnet.private[count.index].id
  route_table_id = var.single_nat_gateway ? aws_route_table.private[0].id : aws_route_table.private[count.index].id
}
