/*
 * Copyright 2023 Thoughtworks, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.thoughtworks.go.server.service;

import com.thoughtworks.go.config.CaseInsensitiveString;
import com.thoughtworks.go.config.JobConfig;
import com.thoughtworks.go.config.PipelineConfig;
import com.thoughtworks.go.config.StageConfig;
import com.thoughtworks.go.config.elastic.ClusterProfile;
import com.thoughtworks.go.config.elastic.ElasticProfile;
import com.thoughtworks.go.config.exceptions.StageNotFoundException;
import com.thoughtworks.go.domain.*;
import com.thoughtworks.go.domain.buildcause.BuildCause;
import com.thoughtworks.go.util.Clock;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class InstanceFactory {

    public Pipeline createPipelineInstance(PipelineConfig pipelineConfig, BuildCause buildCause, SchedulingContext context, String md5, Clock clock) {
        buildCause.assertMaterialsMatch(pipelineConfig.materialConfigs());
        buildCause.assertPipelineConfigAndMaterialRevisionMatch(pipelineConfig);

        EnvironmentVariables variables = EnvironmentVariables.toEnvironmentVariables(pipelineConfig.getVariables());
        variables.overrideWith(buildCause.getVariables());

        return new Pipeline(CaseInsensitiveString.str(pipelineConfig.name()), pipelineConfig.getLabelTemplate(), buildCause, variables, createStageInstance(pipelineConfig.first(), context, md5, clock));
    }

    public Stage createStageInstance(PipelineConfig pipelineConfig, CaseInsensitiveString stageName, SchedulingContext context, String md5, Clock clock) {
        StageConfig stageConfig = pipelineConfig.findBy(stageName);
        if (stageConfig == null) {
            throw new StageNotFoundException(pipelineConfig.name(), stageName);
        }
        return createStageInstance(stageConfig, context, md5, clock);
    }

    public Stage createStageInstance(StageConfig stageConfig, SchedulingContext context, String md5, Clock clock) {
        return new Stage(CaseInsensitiveString.str(stageConfig.name()), createJobInstances(stageConfig, context, clock),
                context.getApprovedBy(), null, stageConfig.approvalType(), stageConfig.isFetchMaterials(), stageConfig.isCleanWorkingDir(), md5, clock);
    }

    public Stage createStageForRerunOfJobs(Stage stage, List<String> jobNames, SchedulingContext context, StageConfig stageConfig, Clock clock, String latestMd5) {
        Stage newStage = stage.createClone();
        newStage.prepareForRerunOf(context, latestMd5);
        createRerunJobs(newStage, jobNames, context, stageConfig, clock);
        return newStage;
    }

    public JobInstances createJobInstance(CaseInsensitiveString stageName, JobConfig jobConfig, SchedulingContext context, Clock clock, JobType.JobNameGenerator jobNameGenerator) {
        JobInstances instances = new JobInstances();
        createJobType(jobConfig.isRunOnAllAgents(), jobConfig.isRunMultipleInstanceType()).createJobInstances(instances, context, jobConfig, CaseInsensitiveString.str(stageName), jobNameGenerator, clock, this);
        return instances;
    }

    private JobInstances createJobInstances(StageConfig stageConfig, SchedulingContext context, Clock clock) {
        JobInstances instances = new JobInstances();
        for (JobConfig jobConfig : stageConfig.getJobs()) {
            JobType.JobNameGenerator nameGenerator = null;
            if (jobConfig.isRunOnAllAgents()) {
                nameGenerator = new RunOnAllAgents.CounterBasedJobNameGenerator(CaseInsensitiveString.str(jobConfig.name()));
            } else if (jobConfig.isRunMultipleInstanceType()) {
                nameGenerator = new RunMultipleInstance.CounterBasedJobNameGenerator(CaseInsensitiveString.str(jobConfig.name()));
            }
            JobInstances configInstances = createJobInstance(stageConfig.name(), jobConfig, context, clock, nameGenerator);
            instances.addAll(configInstances);
        }
        return instances;
    }

    private void createRerunJobs(Stage newStage, List<String> jobNames, SchedulingContext context, StageConfig stageConfig, Clock clock) {
        for (String jobName : jobNames) {
            JobInstances jobInstances = newStage.getJobInstances();
            JobInstance oldJob = jobInstances.getByName(jobName);
            jobInstances.remove(oldJob);
            createJobType(oldJob.isRunOnAllAgents(), oldJob.isRunMultipleInstance()).createRerunInstances(oldJob, jobInstances, context, stageConfig, clock, this);
        }
    }

    private JobType createJobType(boolean runOnAllAgents, boolean runMultipleInstances) {
        if (runOnAllAgents) {
            return new RunOnAllAgents();
        }
        if (runMultipleInstances) {
            return new RunMultipleInstance();
        }
        return new SingleJobInstance();
    }

    public void reallyCreateJobInstance(JobConfig config, JobInstances jobs, String uuid, String jobName, boolean runOnAllAgents, boolean runMultipleInstance, SchedulingContext context, final Clock clock) {
        JobInstance instance = new JobInstance(jobName, clock);
        instance.setPlan(createJobPlan(config, context));
        instance.setAgentUuid(uuid);
        instance.setRunOnAllAgents(runOnAllAgents);
        instance.setRunMultipleInstance(runMultipleInstance);
        jobs.add(instance);
    }

    public JobPlan createJobPlan(JobConfig config, SchedulingContext context) {
        JobIdentifier identifier = new JobIdentifier();
        String elasticProfileId = config.getElasticProfileId();
        String clusterProfileId = null;
        ElasticProfile elasticProfile = null;
        ClusterProfile clusterProfile = null;

        if (elasticProfileId != null) {
            elasticProfile = context.getElasticProfile(elasticProfileId);
            clusterProfileId = elasticProfile.getClusterProfileId();
        }

        if (clusterProfileId != null) {
            clusterProfile = context.getClusterProfile(clusterProfileId);
        }

        final EnvironmentVariables variables = EnvironmentVariables.toEnvironmentVariables(context.overrideEnvironmentVariables(config.getVariables()).getEnvironmentVariablesConfig());

        return new DefaultJobPlan(new Resources(config.resourceConfigs()),
                ArtifactPlan.toArtifactPlans(config.artifactTypeConfigs()),
                -1, identifier, null, variables, new EnvironmentVariables(), elasticProfile, clusterProfile);
    }
}
