package core.aws.resource.elb;

import com.amazonaws.services.elasticloadbalancing.model.ListenerDescription;
import com.amazonaws.services.elasticloadbalancing.model.LoadBalancerDescription;
import core.aws.resource.Resource;
import core.aws.resource.ResourceStatus;
import core.aws.resource.Resources;
import core.aws.resource.ec2.SecurityGroup;
import core.aws.resource.s3.Bucket;
import core.aws.resource.vpc.Subnet;
import core.aws.resource.vpc.SubnetType;
import core.aws.task.elb.CreateELBListenerTask;
import core.aws.task.elb.CreateELBTask;
import core.aws.task.elb.DeleteELBListenerTask;
import core.aws.task.elb.DeleteELBTask;
import core.aws.task.elb.DescribeELBTask;
import core.aws.task.elb.UpdateELBSGTask;
import core.aws.util.Asserts;
import core.aws.util.Lists;
import core.aws.workflow.Tasks;

import java.util.List;
import java.util.Optional;

/**
 * @author neo
 */
public class ELB extends Resource {
    public LoadBalancerDescription remoteELB;
    public String name;
    public boolean listenHTTP;
    public boolean listenHTTPS;
    public ServerCert cert;
    public String healthCheckURL;
    public SecurityGroup securityGroup;
    public Subnet subnet;
    public Bucket accessLogBucket;
    public Optional<String> scheme = Optional.empty();   // currently only allowed value is "internal"
    public String amazonCertARN;

    public ELB(String id) {
        super(id);
    }

    @Override
    public void validate(Resources resources) {
        if (status == ResourceStatus.LOCAL_ONLY && subnet.type == SubnetType.PRIVATE) {
            Asserts.isFalse(scheme.isPresent(), "ELB in private subnet doesn't need scheme, it will be internal by default");
        }

        if (status == ResourceStatus.LOCAL_ONLY) {
            Asserts.isTrue(name.length() <= 32, "max length of elb name is 32");
            Asserts.isTrue(name.matches("[a-zA-Z0-9\\-]+"), "elb name can only contain alphanumeric, and '-'");
        }

        if (status == ResourceStatus.LOCAL_ONLY || status == ResourceStatus.LOCAL_REMOTE) {
            Asserts.isTrue(amazonCertARN != null || cert != null, "https listener requires cert");
        }
    }

    @Override
    protected void createTasks(Tasks tasks) {
        tasks.add(new CreateELBTask(this));
    }

    @Override
    protected void updateTasks(Tasks tasks) {
        if (sgChanged()) {
            tasks.add(new UpdateELBSGTask(this));
        }

        CreateELBListenerTask createELBListenerTask = null;
        List<String> addedProtocols = Lists.newArrayList();
        if (httpListenerAdded()) addedProtocols.add("HTTP");
        if (httpsListenerAdded() || httpsCertChanged()) addedProtocols.add("HTTPS");
        if (!addedProtocols.isEmpty()) {
            createELBListenerTask = new CreateELBListenerTask(this, addedProtocols);
            tasks.add(createELBListenerTask);
        }

        List<String> deletedProtocols = Lists.newArrayList();
        if (httpListenerRemoved()) deletedProtocols.add("HTTP");
        if (httpsListenerRemoved() || httpsCertChanged()) deletedProtocols.add("HTTPS");
        if (!deletedProtocols.isEmpty()) {
            DeleteELBListenerTask deleteELBListenerTask = new DeleteELBListenerTask(this, deletedProtocols);
            if (createELBListenerTask != null) createELBListenerTask.dependsOn(deleteELBListenerTask);
            tasks.add(deleteELBListenerTask);
        }
    }

    @Override
    protected void describeTasks(Tasks tasks) {
        tasks.add(new DescribeELBTask(this));
    }

    private boolean sgChanged() {
        if (securityGroup == null) return false;   // no vpc
        if (securityGroup.remoteSecurityGroup == null) return true;
        if (remoteELB.getSecurityGroups().isEmpty()) return true;
        return !remoteELB.getSecurityGroups().get(0).equals(securityGroup.remoteSecurityGroup.getGroupId());
    }

    @Override
    protected void deleteTasks(Tasks tasks) {
        tasks.add(new DeleteELBTask(this));
    }

    public boolean httpListenerAdded() {
        return listenHTTP && !hasRemoteHTTPListener();
    }

    public boolean httpListenerRemoved() {
        return !listenHTTP && hasRemoteHTTPListener();
    }

    public boolean httpsListenerAdded() {
        Optional<ListenerDescription> remoteHTTPSListener = findRemoteHTTPSListener();
        return listenHTTPS && !remoteHTTPSListener.isPresent();
    }

    public boolean httpsListenerRemoved() {
        Optional<ListenerDescription> remoteHTTPSListener = findRemoteHTTPSListener();
        return !listenHTTPS && remoteHTTPSListener.isPresent();
    }

    public boolean httpsCertChanged() {
        Optional<ListenerDescription> remoteHTTPSListener = findRemoteHTTPSListener();

        if (!listenHTTPS || !remoteHTTPSListener.isPresent()) return false;
        String remoteCertARN = remoteHTTPSListener.get().getListener().getSSLCertificateId();

        if (cert != null) {    // cert files
            if (cert.status == ResourceStatus.LOCAL_ONLY
                || !cert.remoteCert.getServerCertificateMetadata().getArn().equals(remoteCertARN))
                return true;
            if (cert.changed()) return true;
        } else if (!remoteCertARN.equals(amazonCertARN)) {
            return true;
        }

        return false;
    }

    private boolean hasRemoteHTTPListener() {
        return remoteELB.getListenerDescriptions().stream().anyMatch(listener -> "HTTP".equalsIgnoreCase(listener.getListener().getProtocol()));
    }

    private Optional<ListenerDescription> findRemoteHTTPSListener() {
        return remoteELB.getListenerDescriptions().stream().filter(listener -> "HTTPS".equalsIgnoreCase(listener.getListener().getProtocol())).findAny();
    }
}

@@ -1947,32 +1947,48 @@
    %7D%0A%0A        if
+ (listenHTTPS &&
  (status == Reso
@@ -2050,16 +2050,17 @@
 _REMOTE)
+)
  %7B%0A     

fix validation for non-https elb listener