package ru.yandex.qe.dispenser.ws;

import java.time.LocalDate;
import java.util.List;
import java.util.stream.Collectors;

import javax.annotation.ParametersAreNonnullByDefault;
import javax.inject.Inject;
import javax.ws.rs.ForbiddenException;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.Api;
import io.swagger.annotations.Authorization;
import org.jetbrains.annotations.Nullable;
import org.springframework.stereotype.Service;
import ru.yandex.qe.dispenser.domain.Campaign;
import ru.yandex.qe.dispenser.domain.Person;
import ru.yandex.qe.dispenser.domain.ProviderGroupReport;
import ru.yandex.qe.dispenser.domain.dao.bot.provider.group.report.ProviderGroupReportDao;
import ru.yandex.qe.dispenser.domain.dao.campaign.CampaignDao;
import ru.yandex.qe.dispenser.domain.exception.SingleMessageException;
import ru.yandex.qe.dispenser.domain.hierarchy.Session;
import ru.yandex.qe.dispenser.swagger.DispenserSecurityDefinition;
import ru.yandex.qe.dispenser.swagger.SwaggerTags;

import static ru.yandex.qe.dispenser.ws.PermissionService.canUserViewProviderGroupReport;

@ParametersAreNonnullByDefault
@Produces(ServiceBase.APPLICATION_JSON_UTF_8)
@Service("provider-group-report")
@Path("/v1/provider-group-report")
@Api(tags = {SwaggerTags.DISPENSER_API}, authorizations = {@Authorization(value = DispenserSecurityDefinition.AUTHORIZATION_SCHEME_NAME)})
public class ProviderGroupReportService {

    private final ProviderGroupReportDao providerGroupReportDao;
    private final CampaignDao campaignDao;

    @Inject
    public ProviderGroupReportService(final ProviderGroupReportDao providerGroupReportDao, final CampaignDao campaignDao) {
        this.providerGroupReportDao = providerGroupReportDao;
        this.campaignDao = campaignDao;
    }

    /**
     * Get ProviderGroupReport for Campaign
     * @param campaignId of Campaign, for which the report will be returned. If absent, then active Campaign will be used
     * @return ProviderGroupReport for Campaign with campaignId
     */
    @GET
    @Access
    public FullReport get(@Nullable @QueryParam("campaignId") final Long campaignId) {
        final Person person = Session.WHOAMI.get();
        checkUserCanViewProviderGroupReport(person);

        final Campaign campaign;

        if (campaignId == null) {
            campaign = campaignDao.getLastActive()
                    .orElseThrow(() -> SingleMessageException.illegalArgument("no.active.campaign"));
        } else {
            campaign = campaignDao.read(campaignId);
        }

        final List<ProviderGroupReport> byCampaignId = providerGroupReportDao.getByCampaignId(campaign.getId());

        final List<BigOrder> bigOrders = campaign.getBigOrders().stream()
                .map(BigOrder::fromCampaignBigOrder)
                .collect(Collectors.toList());

        final List<Cloud> clouds = byCampaignId.stream()
                .map(Cloud::fromProviderGroupReport)
                .collect(Collectors.toList());

        return new FullReport(bigOrders, clouds);
    }

    public static void checkUserCanViewProviderGroupReport(final Person person) {
        if (!canUserViewProviderGroupReport(person)) {
            throw new ForbiddenException("'" + person.getLogin() + "' is not allowed to view Provider Group Report!");
        }
    }

    public static class FullReport {

        private final List<BigOrder> bigOrders;
        private final List<Cloud> clouds;

        @JsonCreator
        public FullReport(@JsonProperty("bigOrders") final List<BigOrder> bigOrders,
                          @JsonProperty("clouds") final List<Cloud> clouds) {
            this.bigOrders = bigOrders;
            this.clouds = clouds;
        }

        public List<BigOrder> getBigOrders() {
            return bigOrders;
        }

        public List<Cloud> getClouds() {
            return clouds;
        }
    }

    public static class BigOrder {
        private final long id;
        private final LocalDate date;

        @JsonCreator
        public BigOrder(@JsonProperty("id") final long id,
                        @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
                        @JsonProperty("date") final LocalDate date) {
            this.id = id;
            this.date = date;
        }

        public static BigOrder fromCampaignBigOrder(final Campaign.BigOrder bigOrder) {
            return new BigOrder(bigOrder.getBigOrderId(), bigOrder.getDate());
        }

        public long getId() {
            return id;
        }

        @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
        public LocalDate getDate() {
            return date;
        }
    }

    public static class Cloud {
        private final Providers providers;
        private final List<ProviderGroupReport.Location> locations;

        @JsonCreator
        public Cloud(@JsonProperty("providers") final Providers providers,
                     @JsonProperty("locations") final List<ProviderGroupReport.Location> locations) {
            this.providers = providers;
            this.locations = locations;
        }

        public static Cloud fromProviderGroupReport(final ProviderGroupReport providerGroupReport) {
            return new Cloud(Providers.fromProviderGroupReport(providerGroupReport), providerGroupReport.getReport().getLocations());
        }

        public Providers getProviders() {
            return providers;
        }

        public List<ProviderGroupReport.Location> getLocations() {
            return locations;
        }
    }

    public static class Providers {
        protected final String name;
        private final String description;

        @JsonCreator
        public Providers(@JsonProperty("name") final String name,
                         @JsonProperty("description") final String description) {
            this.name = name;
            this.description = description;
        }

        public static Providers fromProviderGroupReport(final ProviderGroupReport providerGroupReport) {
            return new Providers(providerGroupReport.getProviderGroupName(), providerGroupReport.getProviderGroupDescription());
        }

        public String getName() {
            return name;
        }

        public String getDescription() {
            return description;
        }
    }
}
