package com.nirima.jenkins.plugins.docker;

import java.io.IOException;
import java.time.Clock;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.jenkinsci.plugins.docker.commons.credentials.DockerServerEndpoint;
import org.junit.Assert;
import org.mockito.Matchers;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.slf4j.Logger;

import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.ListContainersCmd;
import com.github.dockerjava.api.model.Container;

import hudson.model.Node;
import hudson.model.TaskListener;
import hudson.slaves.SlaveComputer;
import io.jenkins.docker.DockerTransientNode;
import io.jenkins.docker.client.DockerAPI;

public class TestableDockerContainerWatchdog extends DockerContainerWatchdog {
    private static final String UNITTEST_JENKINS_ID = "f1b65f06-be3e-4dac-a760-b17e7592570f";
    private List<Node> allNodes;
    private List<DockerCloud> allClouds;
    private List<DockerTransientNode> nodesRemoved = new LinkedList<>();
    private List<String> containersRemoved = new LinkedList<>();

    public static void setClockOn(DockerContainerWatchdog i, Clock clock) {
        i.setClock(clock);
    }

    @Override
    protected List<DockerCloud> getAllClouds() {
        return Collections.unmodifiableList(allClouds);
    }

    @Override
    protected List<Node> getAllNodes() {
        return allNodes;
    }

    @Override
    protected void removeNode(DockerTransientNode dtn) throws IOException {
        nodesRemoved.add(dtn);
    }

    @Override
    protected String getJenkinsInstanceId() {
        return UNITTEST_JENKINS_ID;
    }

    @Override
    protected boolean stopAndRemoveContainer(DockerAPI dockerApi, Logger logger, String description,
            boolean removeVolumes, String containerId, boolean stop) {
        containersRemoved.add(containerId);
        return true;
    }

    public void setAllNodes(List<Node> allNodes) {
        this.allNodes = allNodes;
    }

    public void setAllClouds(List<DockerCloud> allClouds) {
        this.allClouds = allClouds;
    }

    public List<DockerTransientNode> getAllRemovedNodes() {
        return Collections.unmodifiableList(nodesRemoved);
    }

    public List<String> getContainersRemoved() {
        return Collections.unmodifiableList(containersRemoved);
    }

    public void runExecute() throws IOException, InterruptedException {
        TaskListener mockedListener = Mockito.mock(TaskListener.class);
        Mockito.when(mockedListener.getLogger()).thenReturn(System.out);
        execute(mockedListener);
    }

    public static DockerAPI createMockedDockerAPI(List<Container> containerList) {
        DockerAPI result = Mockito.mock(DockerAPI.class);
        DockerClient client = Mockito.mock(DockerClient.class);
        Mockito.when(result.getClient()).thenReturn(client);
        DockerServerEndpoint dockerServerEndpoint = Mockito.mock(DockerServerEndpoint.class);
        Mockito.when(dockerServerEndpoint.getUri()).thenReturn("tcp://mocked-docker-host:2375");
        Mockito.when(result.getDockerHost()).thenReturn(dockerServerEndpoint);
        ListContainersCmd listContainerCmd = Mockito.mock(ListContainersCmd.class);
        Mockito.when(client.listContainersCmd()).thenReturn(listContainerCmd);
        Mockito.when(listContainerCmd.withShowAll(true)).thenReturn(listContainerCmd);
        Mockito.when(listContainerCmd.withLabelFilter(Matchers.anyMap())).thenAnswer( new Answer<ListContainersCmd>() {
            @Override
            public ListContainersCmd answer(InvocationOnMock invocation) throws Throwable {
                Map<String, String> arg = invocation.getArgumentAt(0, Map.class);
                String jenkinsInstanceIdInFilter = arg.get(DockerContainerLabelKeys.JENKINS_INSTANCE_ID);
                Assert.assertEquals(UNITTEST_JENKINS_ID, jenkinsInstanceIdInFilter);
                return listContainerCmd;
            }
        });
        Mockito.when(listContainerCmd.exec()).thenReturn(containerList);
        return result;
    }

    public static Container createMockedContainer(String containerId, String status, long createdOn, Map<String, String> labels) {
        Container result = Mockito.mock(Container.class);
        Mockito.when(result.getId()).thenReturn(containerId);
        Mockito.when(result.getStatus()).thenReturn(status);
        Mockito.when(result.getCreated()).thenReturn(createdOn);
        Mockito.when(result.getLabels()).thenReturn(labels);
        return result;
    }

    public static DockerTransientNode createMockedDockerTransientNode(String containerId, String nodeName, DockerCloud cloud, boolean offline) {
        DockerTransientNode result = Mockito.mock(DockerTransientNode.class);
        Mockito.when(result.getContainerId()).thenReturn(containerId);
        Mockito.when(result.getNodeName()).thenReturn(nodeName);
        Mockito.when(result.getCloud()).thenReturn(cloud);
        SlaveComputer sc = Mockito.mock(SlaveComputer.class);
        Mockito.when(sc.isOffline()).thenReturn(offline);
        Mockito.when(result.getComputer()).thenReturn(sc);
        return result;
    }
}