# VPC & CIDR Associations =====================================================
# Creates the VPC with a primary CIDR and associates any secondary CIDRs.
# All subnets depend on this block being complete before provisioning.

resource "aws_vpc" "this" {
  cidr_block           = var.cidr_block
  enable_dns_hostnames = var.enable_dns_hostnames
  enable_dns_support   = var.enable_dns_support

  tags = merge(var.tags, local.module_tags, {
    Name          = "${var.name_prefix}-${local.region_abbr}-vpc"
    resource-type = "vpc"
  })
}

resource "aws_vpc_ipv4_cidr_block_association" "this" {
  for_each = toset(var.secondary_cidr_blocks)

  vpc_id     = aws_vpc.this.id
  cidr_block = each.value
}

# Subnets ======================================================================
# 3 tiers: public (IGW-routed), private (NAT-routed), isolated (no egress)
# All tiers depend on CIDR associations being complete before provisioning.

resource "aws_subnet" "public" {
  for_each = var.public_subnets

  vpc_id                  = aws_vpc.this.id
  cidr_block              = each.value.cidr_block
  availability_zone       = each.value.availability_zone
  map_public_ip_on_launch = each.value.map_public_ip

  tags = merge(var.tags, local.module_tags, {
    Name          = "${var.name_prefix}-${local.az_name_to_zone_id[each.value.availability_zone]}-subnet-public-${each.key}"
    resource-type = "subnet"
    reachability  = "public"
  })

  depends_on = [aws_vpc_ipv4_cidr_block_association.this]
}

resource "aws_subnet" "private" {
  for_each = var.private_subnets

  vpc_id            = aws_vpc.this.id
  cidr_block        = each.value.cidr_block
  availability_zone = each.value.availability_zone

  tags = merge(var.tags, local.module_tags, {
    Name          = "${var.name_prefix}-${local.az_name_to_zone_id[each.value.availability_zone]}-subnet-private-${each.key}"
    resource-type = "subnet"
    reachability  = "private"
  })

  depends_on = [aws_vpc_ipv4_cidr_block_association.this]
}

resource "aws_subnet" "isolated" {
  for_each = var.isolated_subnets

  vpc_id            = aws_vpc.this.id
  cidr_block        = each.value.cidr_block
  availability_zone = each.value.availability_zone

  tags = merge(var.tags, local.module_tags, {
    Name          = "${var.name_prefix}-${local.az_name_to_zone_id[each.value.availability_zone]}-subnet-isolated-${each.key}"
    resource-type = "subnet"
    reachability  = "isolated"
  })

  depends_on = [aws_vpc_ipv4_cidr_block_association.this]
}

# Internet Gateway ===========================================================
# Created only when public subnets are defined, skipped otherwise.
# The IGW itself needs no configuration beyond attaching to the VPC.

resource "aws_internet_gateway" "this" {
  for_each = length(var.public_subnets) > 0 ? { igw = true } : {}

  vpc_id = aws_vpc.this.id

  tags = merge(var.tags, local.module_tags, {
    Name          = "${var.name_prefix}-${local.region_abbr}-igw"
    resource-type = "internet-gateway"
  })
}

# NAT Gateways ===============================================================
# Skipped entirely when enable_nat_gateway is false.
# Each NAT gateway requires a static public IP (EIP) and sits in a public subnet.
# Supports single NAT (cost-saving) or one NAT per AZ (high availability).

resource "aws_eip" "nat" {
  for_each = local.nat_gateway_keys

  domain = "vpc"

  tags = merge(var.tags, local.module_tags, {
    Name          = "${var.name_prefix}-${local.az_name_to_zone_id[each.value.availability_zone]}-nat-eip-${each.key}"
    resource-type = "eip"
  })

  depends_on = [aws_internet_gateway.this]
}

resource "aws_nat_gateway" "this" {
  for_each = local.nat_gateway_keys

  allocation_id = aws_eip.nat[each.key].id
  subnet_id = var.single_nat_gateway ? (
    values(aws_subnet.public)[0].id
  ) : aws_subnet.public[each.key].id

  tags = merge(var.tags, local.module_tags, {
    Name          = "${var.name_prefix}-${local.az_name_to_zone_id[each.value.availability_zone]}-nat-${each.key}"
    resource-type = "nat-gateway"
  })

  depends_on = [aws_internet_gateway.this]
}

# Route Tables ===============================================================
# Public subnets share a single RT with a default route to the IGW.
# Private and isolated subnets each get a dedicated RT for per-subnet flexibility.
# Default egress for private subnets is NAT when enable_nat_gateway is true.

# --- Public -----------------------------------------------------------------

resource "aws_route_table" "public" {
  for_each = length(var.public_subnets) > 0 ? { public = true } : {}

  vpc_id = aws_vpc.this.id

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

  tags = merge(var.tags, local.module_tags, {
    Name          = "${var.name_prefix}-${local.region_abbr}-rt-public"
    resource-type = "route-table"
    reachability  = "public"
  })
}

resource "aws_route_table_association" "public" {
  for_each = var.public_subnets

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

# --- Private ----------------------------------------------------------------

resource "aws_route_table" "private" {
  for_each = var.private_subnets

  vpc_id = aws_vpc.this.id

  tags = merge(var.tags, local.module_tags, {
    Name          = "${var.name_prefix}-${local.az_name_to_zone_id[each.value.availability_zone]}-rt-private-${each.key}"
    resource-type = "route-table"
    reachability  = "private"
  })
}

resource "aws_route" "private_nat" {
  for_each = var.enable_nat_gateway ? var.private_subnets : {}

  route_table_id         = aws_route_table.private[each.key].id
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id = var.single_nat_gateway ? (
    aws_nat_gateway.this["single"].id
    ) : (
    [
      for k, v in aws_nat_gateway.this : v.id
      if aws_subnet.public[k].availability_zone == aws_subnet.private[each.key].availability_zone
    ][0]
  )
}

resource "aws_route_table_association" "private" {
  for_each = var.private_subnets

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

# --- Isolated ---------------------------------------------------------------

resource "aws_route_table" "isolated" {
  for_each = var.isolated_subnets

  vpc_id = aws_vpc.this.id

  tags = merge(var.tags, local.module_tags, {
    Name          = "${var.name_prefix}-${local.az_name_to_zone_id[each.value.availability_zone]}-rt-isolated-${each.key}"
    resource-type = "route-table"
    reachability  = "isolated"
  })
}

resource "aws_route_table_association" "isolated" {
  for_each = var.isolated_subnets

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

# TGW VPC Attachment =========================================================
# Created only when transit_gateway_id is set.
# Attachment subnets are derived from tgw_subnet_keys within isolated_subnets.
# RT association and propagation are created when transit_gateway_route_table_id is set,
# otherwise the TGW default route table is used.

resource "aws_ec2_transit_gateway_vpc_attachment" "this" {
  count = var.transit_gateway_id != null ? 1 : 0

  transit_gateway_id = var.transit_gateway_id
  vpc_id             = aws_vpc.this.id
  subnet_ids = [
    for k, v in aws_subnet.isolated : v.id
    if contains(var.tgw_subnet_keys, k)
  ]

  dns_support  = "enable"
  ipv6_support = "disable"

  transit_gateway_default_route_table_association = var.tgw_default_route_table_association
  transit_gateway_default_route_table_propagation = var.tgw_default_route_table_propagation

  tags = merge(var.tags, local.module_tags, {
    Name          = "${var.name_prefix}-${local.region_abbr}-tgw-attach"
    resource-type = "tgw-attachment"
  })
}

resource "aws_ec2_transit_gateway_route_table_association" "this" {
  count = var.transit_gateway_id != null && var.transit_gateway_route_table_id != null ? 1 : 0

  transit_gateway_attachment_id  = aws_ec2_transit_gateway_vpc_attachment.this[0].id
  transit_gateway_route_table_id = var.transit_gateway_route_table_id
}

resource "aws_ec2_transit_gateway_route_table_propagation" "this" {
  count = var.transit_gateway_id != null && var.transit_gateway_route_table_id != null ? 1 : 0

  transit_gateway_attachment_id  = aws_ec2_transit_gateway_vpc_attachment.this[0].id
  transit_gateway_route_table_id = var.transit_gateway_route_table_id
}

# TGW Routes =================================================================
# Default routes to TGW for all isolated subnets except attachment subnets.
# depends_on ensures the attachment is fully available before routes are created,
# avoiding the InvalidTransitGatewayID.NotFound error on fresh TGW deployments.

resource "aws_route" "isolated_tgw" {
  for_each = var.transit_gateway_id != null ? {
    for k, v in var.isolated_subnets : k => v
    if !contains(var.tgw_subnet_keys, k)
  } : {}

  route_table_id         = aws_route_table.isolated[each.key].id
  destination_cidr_block = "0.0.0.0/0"
  transit_gateway_id     = var.transit_gateway_id

  depends_on = [aws_ec2_transit_gateway_vpc_attachment.this]
}

# VPC Flow Logs ==============================================================
# Skipped entirely when enable_flow_logs is false.
# Supports both CloudWatch and S3 destinations, auto-detected from the ARN.
# IAM role and inline policy are created for CloudWatch destinations only.
# The S3 bucket and CloudWatch log group are expected to exist before this module runs.

resource "aws_iam_role" "flow_log" {
  for_each = var.enable_flow_logs ? { role = true } : {}

  name                 = "${var.name_prefix}-${local.region_abbr}-flow-log-role"
  permissions_boundary = var.flow_log_role_permissions_boundary_arn

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Action    = "sts:AssumeRole"
      Principal = { Service = "vpc-flow-logs.amazonaws.com" }
    }]
  })

  tags = merge(var.tags, local.module_tags, {
    Name          = "${var.name_prefix}-${local.region_abbr}-flow-log-role"
    resource-type = "iam-role"
  })

  lifecycle {
    precondition {
      condition     = length("${var.name_prefix}-${local.region_abbr}-flow-log-role") <= 64
      error_message = "Assembled IAM role name exceeds AWS's 64-character limit"
    }
  }
}

resource "aws_iam_role_policy" "flow_log" {
  for_each = var.enable_flow_logs ? { role = true } : {}

  name = "${var.name_prefix}-${local.region_abbr}-flow-log-policy"
  role = aws_iam_role.flow_log["role"].id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Action = [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents",
        "logs:DescribeLogGroups",
        "logs:DescribeLogStreams"
      ]
      Resource = "*"
    }]
  })
}

resource "aws_flow_log" "this" {
  for_each = var.enable_flow_logs ? { flow_log = true } : {}

  vpc_id                   = aws_vpc.this.id
  traffic_type             = var.flow_log_traffic_type
  log_destination_type     = local.flow_log_destination_type
  log_destination          = var.flow_log_destination_arn
  iam_role_arn             = local.flow_log_destination_type == "cloud-watch-logs" ? aws_iam_role.flow_log["role"].arn : null
  max_aggregation_interval = 60

  tags = merge(var.tags, local.module_tags, {
    Name          = "${var.name_prefix}-${local.region_abbr}-flow-log"
    resource-type = "flow-log"
  })
}
