package org.opentosca.container.api.controller;

import java.net.URI;
import java.util.List;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Link;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;

import org.eclipse.winery.model.tosca.TServiceTemplate;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.opentosca.container.api.dto.plan.PlanDTO;
import org.opentosca.container.api.dto.plan.PlanInstanceDTO;
import org.opentosca.container.api.dto.plan.PlanInstanceEventDTO;
import org.opentosca.container.api.dto.plan.PlanInstanceEventListDTO;
import org.opentosca.container.api.dto.plan.PlanInstanceListDTO;
import org.opentosca.container.api.dto.plan.PlanListDTO;
import org.opentosca.container.api.dto.request.CreatePlanInstanceLogEntryRequest;
import org.opentosca.container.api.service.PlanService;
import org.opentosca.container.core.common.uri.UriUtil;
import org.opentosca.container.core.model.csar.Csar;
import org.opentosca.container.core.next.model.PlanInstance;
import org.opentosca.container.core.next.model.PlanInstanceEvent;
import org.opentosca.container.core.next.model.PlanInstanceState;
import org.opentosca.container.core.next.model.PlanType;
import org.opentosca.container.core.tosca.extension.TParameter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Api
// not marked as @RestController because lifecycle is controlled by parent resource
//@RestController
public class BuildPlanController {

    private static final Logger LOGGER = LoggerFactory.getLogger(BuildPlanController.class);

    private static final PlanType PLAN_TYPE = PlanType.BUILD;
    private static final PlanType[] ALL_PLAN_TYPES = PlanType.values();

    private final PlanService planService;
    private final Csar csar;
    private final TServiceTemplate serviceTemplate;

    public BuildPlanController(final Csar csar, final TServiceTemplate serviceTemplate, final PlanService planService) {
        this.planService = planService;
        this.csar = csar;
        this.serviceTemplate = serviceTemplate;
    }

    @GET
    @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    @ApiOperation(value = "Get build plans of service template", response = PlanListDTO.class)
    public Response getBuildPlans(@Context final UriInfo uriInfo) {
        LOGGER.debug("Invoking getBuildPlans");
        PlanListDTO list = new PlanListDTO();
        csar.plans().stream()
            .filter(tplan -> tplan.getPlanType().equals(PLAN_TYPE.toString()))
            .map(p -> {
                final PlanDTO plan = new PlanDTO(p);

                plan.add(Link.fromUri(UriUtil.encode(uriInfo.getAbsolutePathBuilder().path(plan.getId()).path("instances")
                    .build()))
                    .rel("instances").build());
                plan.add(Link.fromUri(UriUtil.encode(uriInfo.getAbsolutePathBuilder().path(plan.getId()).build()))
                    .rel("self").build());
                return plan;
            })
            .forEach(list::add);

        list.add(Link.fromUri(UriUtil.encode(uriInfo.getAbsolutePath())).rel("self").build());
        return Response.ok(list).build();
    }

    @GET
    @Path("/{plan}")
    @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    @ApiOperation(value = "Get a build plan", response = PlanDTO.class)
    public Response getBuildPlan(@ApiParam("ID of build plan") @PathParam("plan") final String plan,
                                 @Context final UriInfo uriInfo) {
        PlanDTO dto = planService.getPlanDto(csar, ALL_PLAN_TYPES, plan);

        dto.add(Link.fromUri(UriUtil.encode(uriInfo.getAbsolutePathBuilder().path("instances").build()))
            .rel("instances").build());
        dto.add(Link.fromUri(UriUtil.encode(uriInfo.getAbsolutePath())).rel("self").build());
        return Response.ok(dto).build();
    }

    @GET
    @Path("/{plan}/instances")
    @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    @ApiOperation(value = "Get build plan instances", response = PlanInstanceListDTO.class)
    public Response getBuildPlanInstances(@ApiParam("ID of build plan") @PathParam("plan") final String plan,
                                          @Context final UriInfo uriInfo) {
        LOGGER.debug("Invoking getBuildPlanInstances");
        List<PlanInstance> planInstances = planService.getPlanInstances(csar, PLAN_TYPE);

        final PlanInstanceListDTO list = new PlanInstanceListDTO();
        planInstances.stream()
            .map(pi -> {
                PlanInstanceDTO dto = PlanInstanceDTO.Converter.convert(pi);
                if (pi.getServiceTemplateInstance() != null) {
                    final URI uri = uriInfo.getBaseUriBuilder()
                        .path("/csars/{csar}/servicetemplates/{servicetemplate}/instances/{instance}")
                        .build(csar.id().csarName(), serviceTemplate.getId(), pi.getServiceTemplateInstance().getId());
                    dto.add(Link.fromUri(UriUtil.encode(uri)).rel("service_template_instance").build());
                }
                dto.add(UriUtil.generateSubResourceLink(uriInfo, pi.getCorrelationId(), false, "self"));
                return dto;
            })
            .forEach(list::add);
        list.add(UriUtil.generateSelfLink(uriInfo));

        return Response.ok(list).build();
    }

    @POST
    @Path("/{plan}/instances")
    @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    @Produces( {MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
    @ApiOperation(value = "Invokes a build plan", response = String.class)
    public Response invokeBuildPlan(@ApiParam("ID of build plan") @PathParam("plan") final String plan,
                                    @Context final UriInfo uriInfo,
                                    @ApiParam(required = true,
                                        value = "plan input parameters") final List<TParameter> parameters) {
        LOGGER.debug("Invoking invokeBuildPlan");
        // We pass -1L because "PlanInvocationEngine.invokePlan()" expects it for build plans
        String correlationId = planService.invokePlan(csar, serviceTemplate, -1L, plan, parameters, PLAN_TYPE);
        return Response.ok(correlationId).build();
    }

    @GET
    @Path("/{plan}/instances/{instance}")
    @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    @ApiOperation(value = "Get a build plan instance", response = PlanInstanceDTO.class)
    public Response getBuildPlanInstance(@ApiParam("ID of build plan") @PathParam("plan") final String plan,
                                         @ApiParam("correlation ID") @PathParam("instance") final String instance,
                                         @Context final UriInfo uriInfo) {
        LOGGER.debug("Invoking getBuildPlanInstance");
        PlanInstance pi = planService.resolvePlanInstance(csar, serviceTemplate, null, plan, instance, PLAN_TYPE);

        final PlanInstanceDTO dto = PlanInstanceDTO.Converter.convert(pi);
        // Add service template instance link
        if (pi.getServiceTemplateInstance() != null) {
            final URI uri = uriInfo.getBaseUriBuilder()
                .path("/csars/{csar}/servicetemplates/{servicetemplate}/instances/{instance}")
                .build(csar.id().csarName(), serviceTemplate.getId(),
                    String.valueOf(pi.getServiceTemplateInstance().getId()));
            dto.add(Link.fromUri(UriUtil.encode(uri)).rel("service_template_instance").build());
        }

        dto.add(UriUtil.generateSubResourceLink(uriInfo, "state", false, "state"));
        dto.add(UriUtil.generateSubResourceLink(uriInfo, "logs", false, "logs"));

        // Add self link
        dto.add(UriUtil.generateSelfLink(uriInfo));

        return Response.ok(dto).build();
    }

    @GET
    @Path("/{plan}/instances/{instance}/state")
    @Produces( {MediaType.TEXT_PLAIN})
    @ApiOperation(value = "Get the state of a build plan instance", response = String.class)
    public Response getBuildPlanInstanceState(@ApiParam("ID of build plan") @PathParam("plan") final String plan,
                                              @ApiParam("correlation ID") @PathParam("instance") final String instance,
                                              @Context final UriInfo uriInfo) {
        LOGGER.debug("Invoking getBuildPlanInstanceState");
        PlanInstance pi = planService.resolvePlanInstance(csar, serviceTemplate, null, plan, instance, PLAN_TYPE);
        return Response.ok(pi.getState().toString()).build();
    }

    @PUT
    @Path("/{plan}/instances/{instance}/state")
    @Consumes( {MediaType.TEXT_PLAIN})
    @ApiOperation(hidden = true, value = "")
    public Response changeBuildPlanInstanceState(@PathParam("plan") final String plan,
                                                 @PathParam("instance") final String instance,
                                                 @Context final UriInfo uriInfo, final String request) {
        LOGGER.debug("Invoking changeBuildPlanInstanceState");
        PlanInstance pi = planService.resolvePlanInstance(csar, serviceTemplate, null, plan, instance, PLAN_TYPE);
        return planService.updatePlanInstanceState(pi, PlanInstanceState.valueOf(request))
            ? Response.ok().build()
            : Response.status(Status.BAD_REQUEST).build();
    }

    @GET
    @Path("/{plan}/instances/{instance}/logs")
    @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    @ApiOperation(value = "Get log entries for a build plan instance", response = PlanInstanceEventDTO.class,
        responseContainer = "list")
    public Response getBuildPlanInstanceLogs(@ApiParam("ID of build plan") @PathParam("plan") final String plan,
                                             @ApiParam("Correlation ID") @PathParam("instance") final String instance,
                                             @Context final UriInfo uriInfo) {
        LOGGER.debug("Invoking getBuildPlanInstanceLogs");
        PlanInstance pi = planService.resolvePlanInstance(csar, serviceTemplate, null, plan, instance, PLAN_TYPE);

        final PlanInstanceDTO piDto = PlanInstanceDTO.Converter.convert(pi);
        final PlanInstanceEventListDTO dto = new PlanInstanceEventListDTO(piDto.getLogs());
        dto.add(UriUtil.generateSelfLink(uriInfo));

        return Response.ok(dto).build();
    }

    @POST
    @Path("/{plan}/instances/{instance}/logs")
    @Consumes( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    @ApiOperation(hidden = true, value = "")
    public Response addBuildPlanLogEntry(@PathParam("plan") final String plan,
                                         @PathParam("instance") final String instance, @Context final UriInfo uriInfo,
                                         final CreatePlanInstanceLogEntryRequest logEntry) {
        LOGGER.debug("Invoking addBuildPlanLogEntry");
        final String entry = logEntry.getLogEntry();
        if (entry == null || entry.length() <= 0) {
            LOGGER.info("Log entry is empty!");
            return Response.status(Status.BAD_REQUEST).build();
        }
        PlanInstance pi = planService.resolvePlanInstance(csar, serviceTemplate, null, plan, instance, PLAN_TYPE);
        final PlanInstanceEvent event = new PlanInstanceEvent("INFO", "PLAN_LOG", entry);
        planService.addLogToPlanInstance(pi, event);

        final URI resourceUri = uriInfo.getAbsolutePath();
        return Response.ok(resourceUri).build();
    }
}