package com.hubspot.baragon.service.resources;

import java.util.Collection;

import javax.validation.Valid;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import com.amazonaws.AmazonClientException;
import com.amazonaws.services.elasticloadbalancingv2.model.CreateListenerRequest;
import com.amazonaws.services.elasticloadbalancingv2.model.CreateRuleRequest;
import com.amazonaws.services.elasticloadbalancingv2.model.CreateTargetGroupRequest;
import com.amazonaws.services.elasticloadbalancingv2.model.Listener;
import com.amazonaws.services.elasticloadbalancingv2.model.ListenerNotFoundException;
import com.amazonaws.services.elasticloadbalancingv2.model.LoadBalancer;
import com.amazonaws.services.elasticloadbalancingv2.model.ModifyListenerRequest;
import com.amazonaws.services.elasticloadbalancingv2.model.ModifyRuleRequest;
import com.amazonaws.services.elasticloadbalancingv2.model.ModifyTargetGroupRequest;
import com.amazonaws.services.elasticloadbalancingv2.model.Rule;
import com.amazonaws.services.elasticloadbalancingv2.model.RuleNotFoundException;
import com.amazonaws.services.elasticloadbalancingv2.model.TargetGroup;
import com.amazonaws.services.elasticloadbalancingv2.model.TargetGroupNotFoundException;
import com.amazonaws.services.elasticloadbalancingv2.model.TargetHealthDescription;
import com.google.common.base.Optional;
import com.google.inject.Inject;
import com.hubspot.baragon.auth.NoAuth;
import com.hubspot.baragon.models.AgentCheckInResponse;
import com.hubspot.baragon.service.config.ElbConfiguration;
import com.hubspot.baragon.service.elb.ApplicationLoadBalancer;
import com.hubspot.baragon.service.exceptions.BaragonWebException;

@Path("/albs")
@Produces(MediaType.APPLICATION_JSON)
public class AlbResource {
  private final ApplicationLoadBalancer applicationLoadBalancer;
  private final Optional<ElbConfiguration> config;

  @Inject
  public AlbResource(ApplicationLoadBalancer applicationLoadBalancer,
                     Optional<ElbConfiguration> config) {
    this.applicationLoadBalancer = applicationLoadBalancer;
    this.config = config;
  }

  @GET
  @NoAuth
  @Path("/load-balancers")
  public Collection<LoadBalancer> getAllLoadBalancers() {
    if (config.isPresent()) {
      return applicationLoadBalancer.getAllLoadBalancers();
    } else {
      throw new BaragonWebException("ElbSync and related actions are not currently enabled");
    }
  }

  @GET
  @NoAuth
  @Path("/load-balancers/{elbName}")
  public LoadBalancer getLoadBalancer(@PathParam("elbName") String elbName) {
    if (config.isPresent()) {
      try {
        Optional<LoadBalancer> maybeLoadBalancer = applicationLoadBalancer.getLoadBalancer(elbName);
        if (maybeLoadBalancer.isPresent()) {
          return maybeLoadBalancer.get();
        } else {
          throw new WebApplicationException(String.format("ELB %s not found", elbName), Status.NOT_FOUND);
        }
      } catch (AmazonClientException exn) {
        throw new BaragonWebException(String.format("AWS client exception %s", exn));
      }
    } else {
      throw new BaragonWebException("ElbSync and related actions are not currently enabled");
    }
  }

  @GET
  @NoAuth
  @Path("/load-balancers/{elbName}/listeners")
  public Collection<Listener> getListeners(@PathParam("elbName") String elbName) {
    if (config.isPresent()) {
      try {
        return applicationLoadBalancer.getListenersForElb(elbName);
      } catch (AmazonClientException exn) {
        throw new BaragonWebException(String.format("AWS client exception %s", exn));
      }
    } else {
      throw new BaragonWebException("ElbSync and related actions are not currently enabled");
    }
  }

  @POST
  @Path("/load-balancers/{elbName}/listeners")
  public Listener createListeners(@PathParam("elbName") String elbName,
                                  @Valid CreateListenerRequest createListenerRequest) {
    if (config.isPresent()) {
        Optional<LoadBalancer> maybeLoadBalancer = applicationLoadBalancer
            .getLoadBalancer(elbName);
        if (maybeLoadBalancer.isPresent()) {
          return applicationLoadBalancer
              .createListener(createListenerRequest
                  .withLoadBalancerArn(maybeLoadBalancer.get().getLoadBalancerArn()));
        } else {
          throw new WebApplicationException(String.format("Could not find an elb with name %s", elbName), Status.NOT_FOUND);
        }
    } else {
      throw new BaragonWebException("ElbSync and related actions are not currently enabled");
    }
  }

  @POST
  @Path("/listeners/{listenerArn:.+}")
  public Listener updateListener(@PathParam("elbName") String elbName,
                                 @PathParam("listenerArn") String listenerArn,
                                 @Valid ModifyListenerRequest modifyListenerRequest) {
    if (config.isPresent()) {
      try {
        return applicationLoadBalancer
            .modifyListener(modifyListenerRequest.withListenerArn(listenerArn));
      } catch (ListenerNotFoundException notFound) {
        throw new WebApplicationException(String.format("No listener with ARN %s found", listenerArn), Status.NOT_FOUND);
      } catch (AmazonClientException exn) {
        throw new BaragonWebException(String.format("AWS client exception %s", exn));
      }
    } else {
      throw new BaragonWebException("ElbSync and related actions are not currently enabled");
    }
  }

  @DELETE
  @Path("/listeners/{listenerArn:.+}")
  public Response removeListener(@PathParam("elbName") String elbName,
                                 @PathParam("listenerArn") String listenerArn) {
    if (config.isPresent()) {
      try {
        applicationLoadBalancer.deleteListener(listenerArn);
        return Response.noContent().build();
      } catch (ListenerNotFoundException notFound) {
        throw new WebApplicationException(String.format("No listener with ARN %s found", listenerArn), Status.NOT_FOUND);
      } catch (AmazonClientException exn) {
        throw new BaragonWebException(String.format("AWS client exception %s", exn));
      }
    } else {
      throw new BaragonWebException("ElbSync and related actions are not currently enabled");
    }
  }

  @GET
  @NoAuth
  @Path("/listeners/rules/{listenerArn:.+}")
  public Collection<Rule> getRules(@PathParam("elbName") String elbName,
                                   @PathParam("listenerArn") String listenerArn) {
    if (config.isPresent()) {
      try {
        return applicationLoadBalancer.getRulesByListener(listenerArn);
      } catch (ListenerNotFoundException notFound) {
        throw new WebApplicationException(String.format("Listener with ARN %s not found", listenerArn), Status.NOT_FOUND);
      } catch (AmazonClientException exn) {
        throw new BaragonWebException(String.format("AWS client exception %s", exn));
      }
    } else {
      throw new BaragonWebException("ElbSync and related actions are not currently enabled");
    }
  }

  @POST
  @Path("/listeners/rules/{listenerArn:.+}")
  public Rule createRule(@PathParam("elbName") String elbName,
                         @PathParam("listenerArn") String listenerArn,
                         @Valid CreateRuleRequest createRuleRequest) {
    if (config.isPresent()) {
      try {
        return applicationLoadBalancer
            .createRule(createRuleRequest
                .withListenerArn(listenerArn));
      } catch (ListenerNotFoundException notFound) {
        throw new WebApplicationException(String.format("Listener with ARN %s not found", listenerArn), Status.NOT_FOUND);
      } catch (AmazonClientException exn) {
        throw new BaragonWebException(String.format("AWS client exception %s", exn));
      }
    } else {
      throw new BaragonWebException("ElbSync and related actions are not currently enabled");
    }
  }

  @POST
  @Path("/rules/{ruleArn:.+}")
  public Rule updateRule(@PathParam("elbName") String elbName,
                         @PathParam("listenerArn") String listenerArn,
                         @PathParam("ruleArn") String ruleArn,
                         @Valid ModifyRuleRequest modifyRuleRequest) {
    if (config.isPresent()) {
      try {
        return applicationLoadBalancer
            .modifyRule(modifyRuleRequest.withRuleArn(ruleArn));
      } catch (RuleNotFoundException notFound) {
        throw new WebApplicationException(String.format("Rule with ARN %s found", ruleArn), Status.NOT_FOUND);
      } catch (AmazonClientException exn) {
        throw new BaragonWebException(String.format("AWS client exception %s", exn));
      }
    } else {
      throw new BaragonWebException("ElbSync and related actions are not currently enabled");
    }
  }

  @DELETE
  @Path("/rules/{ruleArn:.+}")
  public Response deleteRule(@PathParam("elbName") String elbName,
                             @PathParam("listenerArn") String listenerArn,
                             @PathParam("ruleArn") String ruleArn) {
    if (config.isPresent()) {
      try {
        applicationLoadBalancer.deleteRule(ruleArn);
        return Response.noContent().build();
      } catch (RuleNotFoundException notFound) {
        throw new WebApplicationException(String.format("Rule with ARN %s not found", ruleArn), Status.NOT_FOUND);
      } catch (AmazonClientException exn) {
        throw new BaragonWebException(String.format("Amazon client exception %s", exn));
      }
    } else {
      throw new BaragonWebException("ElbSync and related actions are not currently enabled");
    }
  }

  @GET
  @NoAuth
  @Path("/target-groups")
  public Collection<TargetGroup> getAllTargetGroups() {
    if (config.isPresent()) {
      return applicationLoadBalancer.getAllTargetGroups();
    } else {
      throw new BaragonWebException("ElbSync and related actions are not currently enabled");
    }
  }

  @POST
  @PathParam("/target-groups")
  public TargetGroup createTargetGroup(@Valid CreateTargetGroupRequest createTargetGroupRequest) {
    if (config.isPresent()) {
      try {
        return applicationLoadBalancer.createTargetGroup(createTargetGroupRequest);
      } catch (AmazonClientException exn) {
        throw new BaragonWebException(String.format("AWS client exception %s", exn));
      }
    } else {
      throw new BaragonWebException("ElbSync and related actions are not currently enabled");
    }
  }

  @GET
  @NoAuth
  @Path("/target-groups/{targetGroup}")
  public TargetGroup getTargetGroup(@PathParam("targetGroup") String targetGroup) {
    if (config.isPresent()) {
      try {
        Optional<TargetGroup> maybeTargetGroup = applicationLoadBalancer.getTargetGroup(targetGroup);
        if (maybeTargetGroup.isPresent()) {
          return maybeTargetGroup.get();
        } else {
          throw new WebApplicationException(String.format("TargetGroup with name %s not found", targetGroup), Status.NOT_FOUND);
        }
      } catch (AmazonClientException exn) {
        throw new BaragonWebException(String.format("AWS Client exception %s", exn));
      }
    } else {
      throw new BaragonWebException("ElbSync and related actions are not currently enabled");
    }
  }

  @POST
  @Path("/target-groups/{targetGroup}")
  public TargetGroup modifyTargetGroup(@PathParam("targetGroup") String targetGroup,
                                       @Valid ModifyTargetGroupRequest modifyTargetGroupRequest) {
    if (config.isPresent()) {
      try {
        return applicationLoadBalancer
            .modifyTargetGroup(modifyTargetGroupRequest);
      } catch (TargetGroupNotFoundException notFound) {
        throw new WebApplicationException(String.format("Target group %s not found", targetGroup), Status.NOT_FOUND);
      } catch (AmazonClientException exn) {
        throw new BaragonWebException(String.format("AWS client exception %s", exn));
      }
    } else {
      throw new BaragonWebException("ElbSync and related actions are not currently enabled");
    }
  }

  @GET
  @NoAuth
  @Path("/target-groups/{targetGroup}/targets")
  public Collection<TargetHealthDescription> getTargets(@PathParam("targetGroup") String targetGroup) {
    if (config.isPresent()) {
      try {
        Optional<TargetGroup> maybeTargetGroup = applicationLoadBalancer.getTargetGroup(targetGroup);
        if (maybeTargetGroup.isPresent()) {
          return applicationLoadBalancer.getTargetsOn(maybeTargetGroup.get());
        } else {
          throw new WebApplicationException(String.format("TargetGroup with name %s not found", targetGroup), Status.NOT_FOUND);
        }
      } catch (AmazonClientException exn) {
        throw new BaragonWebException(String.format("AWS Client exception %s", exn));
      }
    } else {
      throw new BaragonWebException("ElbSync and related actions are not currently enabled");
    }
  }

  @DELETE
  @Path("/target-groups/{targetGroup}/targets/{instanceId}")
  public AgentCheckInResponse removeFromTargetGroup(@PathParam("targetGroup") String targetGroup,
                                                    @PathParam("instanceId") String instanceId) {
    if (instanceId == null) {
      throw new BaragonWebException("Must provide instance ID to remove target from group");
    } else if (config.isPresent()) {
      AgentCheckInResponse result = applicationLoadBalancer.removeInstance(instanceId, targetGroup);
      if (result.getExceptionMessage().isPresent()) {
        throw new WebApplicationException(result.getExceptionMessage().get(), Status.INTERNAL_SERVER_ERROR);
      }
      return result;
    } else {
      throw new BaragonWebException("ElbSync and related actions not currently enabled");
    }
  }

  @POST
  @Path("/target-group/{targetGroup}/targets")
  public AgentCheckInResponse addToTargetGroup(@PathParam("targetGroup") String targetGroup,
                                                 @QueryParam("instanceId") String instanceId) {
    if (instanceId == null) {
      throw new BaragonWebException("Must provide instance ID to add target to group");
    } else if (config.isPresent()) {
      try {
        return applicationLoadBalancer.registerInstance(instanceId, targetGroup);
      } catch (TargetGroupNotFoundException notFound) {
        throw new WebApplicationException(String.format("Target group %s not found", targetGroup), Status.NOT_FOUND);
      } catch (AmazonClientException exn) {
        throw new BaragonWebException(String.format("Failed to add instance %s to target group %s", instanceId, targetGroup));
      }

    } else {
      throw new BaragonWebException("ElbSync and related actions are not currently enabled");
    }
  }
}