data "aws_vpc" "selected" {
  id = var.vpc_id
}

# Lookup managed prefix lists for private subnets
data "aws_ec2_managed_prefix_lists" "private_subnets" {
  count = var.allow_vpc_private_subnets ? 1 : 0

  tags = {
    environment  = var.environment
    subnet_group = "all"
    tier         = "private"
    vpc_id       = var.vpc_id
  }
}

# Lookup additional ingress prefix lists
data "aws_ec2_managed_prefix_list" "ingress_prefix_lists" {
  for_each = toset(var.ingress_prefix_list_names)
  name     = each.value
}

data "aws_vpc_endpoint" "s3_interface" {
  service_name = "com.amazonaws.${var.aws_region}.s3"
  vpc_id       = data.aws_vpc.selected.id
  state        = "available"

  filter {
    name   = "vpc-endpoint-type"
    values = ["Interface"]
  }

  dynamic "filter" {
    for_each = var.vpc_endpoint_name != "" ? [1] : []
    content {
      name   = "tag:Name"
      values = [var.vpc_endpoint_name]
    }
  }
}

# Fetch each ENI to obtain its private IPs for target registration
data "aws_network_interface" "s3_endpoint_enis" {
  for_each = toset(data.aws_vpc_endpoint.s3_interface.network_interface_ids)
  id       = each.value
}

# Security group for the load balancer
resource "aws_security_group" "load_balancer_security_group" {
  name        = "${var.environment}-${var.service_name}-load-balancer-security-group"
  description = "Security group for load balancer"
  vpc_id      = data.aws_vpc.selected.id

  tags = merge(local.combined_resource_tags, {
    Name = "${var.environment}-${var.service_name}-load-balancer-security-group"
    type = "lb"
  })
}

resource "aws_vpc_security_group_ingress_rule" "load_balancer_cidr" {
  for_each          = toset(var.ingress_cidr_blocks)
  security_group_id = aws_security_group.load_balancer_security_group.id
  cidr_ipv4         = each.key
  from_port         = var.load_balancer_listener_port
  to_port           = var.load_balancer_listener_port
  ip_protocol       = "tcp"
  description       = "Allow HTTPS from ${each.key}"

  tags = local.combined_resource_tags
}

resource "aws_vpc_security_group_ingress_rule" "load_balancer_prefix_list" {
  for_each          = data.aws_ec2_managed_prefix_list.ingress_prefix_lists
  security_group_id = aws_security_group.load_balancer_security_group.id
  prefix_list_id    = each.value.id
  from_port         = var.load_balancer_listener_port
  to_port           = var.load_balancer_listener_port
  ip_protocol       = "tcp"
  description       = "Allow HTTPS from ${each.key} prefix list"

  tags = local.combined_resource_tags
}

resource "aws_vpc_security_group_ingress_rule" "load_balancer_private_subnets" {
  for_each          = var.allow_vpc_private_subnets ? toset(data.aws_ec2_managed_prefix_lists.private_subnets[0].ids) : toset([])
  security_group_id = aws_security_group.load_balancer_security_group.id
  prefix_list_id    = each.key
  from_port         = var.load_balancer_listener_port
  to_port           = var.load_balancer_listener_port
  ip_protocol       = "tcp"
  description       = "Allow HTTPS from VPC private subnets"

  tags = local.combined_resource_tags
}

resource "aws_vpc_security_group_egress_rule" "load_balancer_https_egress" {
  security_group_id = aws_security_group.load_balancer_security_group.id
  cidr_ipv4         = data.aws_vpc.selected.cidr_block
  from_port         = 443
  to_port           = 443
  ip_protocol       = "tcp"
  description       = "Allow HTTPS to VPC"

  tags = local.combined_resource_tags
}

# Target group using IP targets that point to the S3 interface endpoint ENI IPs
resource "aws_lb_target_group" "s3_endpoint_ip_target_group" {
  name        = "${var.environment}-${var.service_name}"
  port        = 443
  protocol    = "HTTPS"
  target_type = "ip"
  vpc_id      = data.aws_vpc.selected.id

  health_check {
    enabled             = true
    interval            = var.health_check_interval
    timeout             = var.health_check_timeout
    healthy_threshold   = var.health_check_healthy_threshold
    unhealthy_threshold = var.health_check_unhealthy_threshold
    protocol            = "HTTPS"
    path                = var.health_check_path
    matcher             = var.health_check_matcher
  }

  tags = local.combined_resource_tags
}

# Attach each IP to the target group
resource "aws_lb_target_group_attachment" "s3_endpoint_ip_attachments" {
  for_each         = { for eni_key, eni in data.aws_network_interface.s3_endpoint_enis : eni_key => eni.private_ip }
  target_group_arn = aws_lb_target_group.s3_endpoint_ip_target_group.arn
  target_id        = each.value
  port             = 443
  depends_on       = [data.aws_network_interface.s3_endpoint_enis]
}

data "aws_route53_zone" "zone" {
  provider     = aws.dns
  count        = var.route53_record_enabled ? 1 : 0
  name         = var.route53_zone_name
  private_zone = var.route53_is_private_zone
}

resource "aws_route53_record" "alb_service" {
  provider = aws.dns
  count    = var.route53_record_enabled ? 1 : 0
  zone_id  = data.aws_route53_zone.zone[0].zone_id
  name     = var.service_name
  type     = "A"

  alias {
    name                   = aws_lb.application_load_balancer.dns_name
    zone_id                = aws_lb.application_load_balancer.zone_id
    evaluate_target_health = false
  }
}

# S3 bucket for the SPA
module "s3_bucket" {
  source = "git@github.com:theorchard/terraform-s3.git//modules/s3_bucket?ref=3.15.1"

  env                                                       = var.environment
  application_family                                        = var.application_family
  bucket_name                                               = var.service_name # this is overridden by custom_bucket_name but the module requires it to be set
  custom_bucket_name                                        = var.fully_qualified_domain_name
  bucket_policy_overrides                                   = concat([data.aws_iam_policy_document.s3_vpc_endpoint_access.json], var.s3_bucket_policy_overrides != null ? var.s3_bucket_policy_overrides : [])
  apply_replication_configuration                           = var.s3_apply_replication_configuration
  apply_replication_destination_configuration               = var.s3_apply_replication_destination_configuration
  custom_logging_bucket                                     = var.s3_custom_logging_bucket
  custom_logging_prefix                                     = var.s3_custom_logging_prefix
  lifecycle_rules_options_noncurrent_version_transition     = var.s3_lifecycle_rules_options_noncurrent_version_transition
  lifecycle_rules_options_current_version_transition        = var.s3_lifecycle_rules_options_current_version_transition
  lifecycle_rules_options_noncurrent_version_expiration     = var.s3_lifecycle_rules_options_noncurrent_version_expiration
  lifecycle_rules_options_current_version_expiration        = var.s3_lifecycle_rules_options_current_version_expiration
  lifecycle_rules_abort_incomplete_multipart_upload_days    = var.s3_lifecycle_rules_abort_incomplete_multipart_upload_days
  lifecycle_rules_expired_object_delete_markers_expiration  = var.s3_lifecycle_rules_expired_object_delete_markers_expiration
  s3_read_only_policy                                       = var.s3_read_only_policy
  bucket_cors_rule                                          = var.s3_bucket_cors_rule
  additional_tags                                           = merge(local.combined_resource_tags, var.additional_tags)

  apply_server_side_encryption_by_default = {
    sse_algorithm = "AES256"
  }
}

# Bucket policy allowing access via the specific VPC endpoint to objects
data "aws_iam_policy_document" "s3_vpc_endpoint_access" {
  statement {
    sid = "AllowVPCEGetObject"
    actions = [
      "s3:GetObject"
    ]
    resources = [
      module.s3_bucket.s3_bucket_arn_output,
      "${module.s3_bucket.s3_bucket_arn_output}/*"
    ]
    principals {
      type        = "AWS"
      identifiers = ["*"]
    }
    condition {
      test     = "StringEquals"
      variable = "aws:sourceVpce"
      values   = [data.aws_vpc_endpoint.s3_interface.id]
    }
  }

  statement {
    sid = "AllowDeployRoleBucketOperations"
    actions = [
      "s3:ListBucket",
      "s3:ListBucketVersions",
      "s3:GetBucketLocation",
    ]
    resources = [
      module.s3_bucket.s3_bucket_arn_output,
    ]
    principals {
      type        = "AWS"
      identifiers = [var.deploy_role_arn]
    }
  }

  statement {
    sid = "AllowDeployRoleObjectOperations"
    actions = [
      "s3:GetObject*",
      "s3:PutObject*"
    ]
    resources = [
      "${module.s3_bucket.s3_bucket_arn_output}/*"
    ]
    principals {
      type        = "AWS"
      identifiers = [var.deploy_role_arn]
    }
  }
}

data "aws_acm_certificate" "load_balancer_tls_certificate" {
  domain      = var.certificate_domain
  statuses    = ["ISSUED"]
  most_recent = true
}

# Application Load Balancer
resource "aws_lb" "application_load_balancer" {
  name                       = "${var.environment}-${var.service_name}"
  internal                   = var.load_balancer_is_internal
  load_balancer_type         = "application"
  idle_timeout               = var.load_balancer_idle_timeout
  security_groups            = [aws_security_group.load_balancer_security_group.id]
  subnets                    = var.load_balancer_subnet_ids
  enable_deletion_protection = var.load_balancer_enable_deletion_protection

  access_logs {
    bucket  = local.load_balancer_access_logs_s3_bucket_name
    prefix  = local.load_balancer_access_logs_prefix
    enabled = true
  }

  tags = local.combined_resource_tags
}

# HTTPS Listener
resource "aws_lb_listener" "load_balancer_https_listener" {
  load_balancer_arn = aws_lb.application_load_balancer.arn
  port              = var.load_balancer_listener_port
  protocol          = "HTTPS"
  ssl_policy        = var.load_balancer_ssl_policy
  certificate_arn   = data.aws_acm_certificate.load_balancer_tls_certificate.arn

  routing_http_response_access_control_allow_origin_header_value  = var.enable_pull_request_instances ? "*" : null
  routing_http_response_access_control_allow_methods_header_value = var.enable_pull_request_instances ? "GET, HEAD" : null

  default_action {
    target_group_arn = aws_lb_target_group.s3_endpoint_ip_target_group.arn
    type             = "forward"
  }

  depends_on = [aws_lb_target_group.s3_endpoint_ip_target_group]
  tags       = local.combined_resource_tags
}

# Listener rule for static assets and specific paths
resource "aws_lb_listener_rule" "forward_paths" {
  listener_arn = aws_lb_listener.load_balancer_https_listener.arn
  priority     = var.static_paths_rule_priority

  action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.s3_endpoint_ip_target_group.arn
  }

  condition {
    path_pattern {
      values = local.path_patterns_for_static_assets
    }
  }

  tags = local.combined_resource_tags
}

# Listener rule for URL rewriting to index.html for SPA routing
resource "aws_lb_listener_rule" "rewrite_to_index" {
  listener_arn = aws_lb_listener.load_balancer_https_listener.arn
  priority     = var.spa_rewrite_rule_priority

  action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.s3_endpoint_ip_target_group.arn

    forward {
      target_group {
        arn = aws_lb_target_group.s3_endpoint_ip_target_group.arn
      }
    }
  }

  transform {
    type = "url-rewrite"
    url_rewrite_config {
      rewrite {
        regex   = var.spa_rewrite_regex
        replace = local.spa_rewrite_target
      }
    }
  }

  condition {
    path_pattern {
      values = var.spa_rewrite_path_patterns
    }
  }

  tags = local.combined_resource_tags
}

data "aws_wafv2_web_acl" "default_waf" {
  name  = local.waf_name
  scope = "REGIONAL"
}

# WAF association
resource "aws_wafv2_web_acl_association" "waf_association" {
  resource_arn = aws_lb.application_load_balancer.arn
  web_acl_arn  = data.aws_wafv2_web_acl.default_waf.arn
}
