/*
 * 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.config;

import com.thoughtworks.go.config.preprocessor.SkipParameterResolution;
import com.thoughtworks.go.config.validation.NameTypeValidator;
import com.thoughtworks.go.domain.ConfigErrors;
import com.thoughtworks.go.service.TaskFactory;
import com.thoughtworks.go.util.GoConstants;

import java.util.List;
import java.util.Map;

/**
 * @understands the configuration for a stage
 */
@ConfigTag("stage")
public class StageConfig implements Validatable, ParamsAttributeAware, EnvironmentVariableScope {
    @SkipParameterResolution
    @ConfigAttribute(value = "name", optional = false)
    private CaseInsensitiveString name;

    @ConfigAttribute(value = "fetchMaterials") private boolean fetchMaterials = DEFAULT_FETCH_MATERIALS;
    @ConfigAttribute(value = "artifactCleanupProhibited") private boolean artifactCleanupProhibited = false;
    @ConfigAttribute(value = "cleanWorkingDir") private boolean cleanWorkingDir = DEFAULT_CLEAN_WORKING_DIR;
    @ConfigSubtag(optional = true) private Approval approval = Approval.automaticApproval();
    @ConfigSubtag private EnvironmentVariablesConfig variables = new EnvironmentVariablesConfig();
    @ConfigSubtag(optional = false) private JobConfigs jobConfigs;

    private ConfigErrors errors = new ConfigErrors();

    public static final boolean DEFAULT_FETCH_MATERIALS = true;
    public static final boolean DEFAULT_CLEAN_WORKING_DIR = false;

    public static final String NAME = "name";
    public static final String APPROVAL = "approval";
    public static final String JOBS = "jobs";
    public static final String FETCH_MATERIALS = "fetchMaterials";
    public static final String CLEAN_WORKING_DIR = "cleanWorkingDir";
    public static final String ENVIRONMENT_VARIABLES = "variables";
    public static final String OPERATE_USERS = "operateUsers";
    public static final String OPERATE_ROLES = "operateRoles";
    public static final String SECURITY_MODE = "securityMode";
    public static final String DEFAULT_NAME = "defaultStage";
    public static final String ARTIFACT_CLEANUP_PROHIBITED = "artifactCleanupProhibited";

    public StageConfig() {
    }

    public StageConfig(final CaseInsensitiveString name, JobConfigs jobConfigs) {
        this();
        this.name = name;
        this.jobConfigs = jobConfigs;
    }

    public StageConfig(final CaseInsensitiveString name, JobConfigs jobConfigs, Approval approval) {
        this(name, DEFAULT_FETCH_MATERIALS, DEFAULT_CLEAN_WORKING_DIR, approval, false, jobConfigs);
    }

    public StageConfig(final CaseInsensitiveString name, boolean fetchMaterials, boolean cleanWorkingDir, Approval approval, boolean artifactCleanupProhibited, JobConfigs jobConfigs) {
        this(name, jobConfigs);
        this.fetchMaterials = fetchMaterials;
        this.cleanWorkingDir = cleanWorkingDir;
        this.approval = approval;
        this.artifactCleanupProhibited = artifactCleanupProhibited;
    }

    @Override
    public CaseInsensitiveString name() {
        return name;
    }

    public void setName(CaseInsensitiveString name) {
        this.name = name;
    }


    /* Used in view */
    public boolean isFetchMaterials() {
        return fetchMaterials;
    }

    /* Used in view */
    public boolean isCleanWorkingDir() {
        return cleanWorkingDir;
    }

    public JobConfig jobConfigByInstanceName(String jobInstanceName, boolean ignoreCase) {
        for (JobConfig jobConfig : jobConfigs) {
            if (jobConfig.isInstanceOf(jobInstanceName, ignoreCase)) {
                return jobConfig;
            }
        }
        return null;
    }

    public JobConfig jobConfigByConfigName(final CaseInsensitiveString jobName) {
        for (JobConfig jobConfig : jobConfigs) {
            if (jobConfig.name().equals(jobName)) {
                return jobConfig;
            }
        }
        return null;
    }

    public JobConfig jobConfigByConfigName(String jobName) {
        return jobConfigByConfigName(new CaseInsensitiveString(jobName));
    }

    // TODO - #2491 - rename jobConfig to job

    public JobConfigs allBuildPlans() {
        return jobConfigs;
    }

    public String approvalType() {
        return requiresApproval() ? GoConstants.APPROVAL_MANUAL : GoConstants.APPROVAL_SUCCESS;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        StageConfig that = (StageConfig) o;

        if (fetchMaterials != that.fetchMaterials) {
            return false;
        }
        if (artifactCleanupProhibited != that.artifactCleanupProhibited) {
            return false;
        }
        if (cleanWorkingDir != that.cleanWorkingDir) {
            return false;
        }
        if (approval != null ? !approval.equals(that.approval) : that.approval != null) {
            return false;
        }
        if (jobConfigs != null ? !jobConfigs.equals(that.jobConfigs) : that.jobConfigs != null) {
            return false;
        }
        if (name != null ? !name.equals(that.name) : that.name != null) {
            return false;
        }
        if (variables != null ? !variables.equals(that.variables) : that.variables != null) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + (fetchMaterials ? 1 : 0);
        result = 31 * result + (artifactCleanupProhibited ? 1 : 0);
        result = 31 * result + (cleanWorkingDir ? 1 : 0);
        result = 31 * result + (approval != null ? approval.hashCode() : 0);
        result = 31 * result + (variables != null ? variables.hashCode() : 0);
        result = 31 * result + (jobConfigs != null ? jobConfigs.hashCode() : 0);
        return result;
    }

    public boolean requiresApproval() {
        return approval.isManual();
    }

    public void updateApproval(Approval approval) {
        this.approval = approval;
    }

    public boolean supportAutoApproval() {
        return !requiresApproval();
    }

    public Approval getApproval() {
        return approval;
    }

    public void setApproval(Approval approval) {
        this.approval = approval;
    }

    public boolean hasOperatePermissionDefined() {
        return this.approval.isAuthorizationDefined();
    }

    public void setVariables(EnvironmentVariablesConfig variables) {
        this.variables = variables;
    }

    public EnvironmentVariablesConfig getVariables() {
        return variables;
    }

    public EnvironmentVariablesConfig getPlainTextVariables() {
        return variables.getPlainTextVariables();
    }

    public EnvironmentVariablesConfig getSecureVariables() {
        return variables.getSecureVariables();
    }

    public boolean hasVariableInScope(String variableName) {
        if (variables.hasVariable(variableName)) {
            return true;
        }
        for (JobConfig jobConfig : jobConfigs) {
            if (jobConfig.hasVariable(variableName)) {
                return true;
            }
        }
        return false;
    }

    public boolean hasTests() {
        for (JobConfig job : jobConfigs) {
            if (job.hasTests()) {
                return true;
            }
        }
        return false;
    }

    public void setFetchMaterials(boolean fetchMaterials) {
        this.fetchMaterials = fetchMaterials;
    }

    public void setCleanWorkingDir(boolean cleanWorkingDir) {
        this.cleanWorkingDir = cleanWorkingDir;
    }

    public boolean validateTree(ValidationContext validationContext) {
        validate(validationContext);
        boolean isValid = errors.isEmpty();
        ValidationContext contextForChildren = validationContext.withParent(this);
        isValid = jobConfigs.validateTree(contextForChildren) && isValid;
        isValid = approval.validateTree(contextForChildren) && isValid;
        isValid = variables.validateTree(contextForChildren) && isValid;
        return isValid;
    }

    public void encryptSecureProperties(CruiseConfig preprocessedConfig, PipelineConfig preprocessedPipelineConfig, StageConfig preprocessedStageConfig) {
        for (JobConfig jobConfig : getJobs()) {
            jobConfig.encryptSecureProperties(preprocessedConfig, preprocessedPipelineConfig, preprocessedStageConfig.jobConfigByConfigName(jobConfig.name()));
        }
    }

    public void encryptSecureProperties(CruiseConfig preprocessedConfig, PipelineTemplateConfig pipelineTemplateConfig) {
        for (JobConfig jobConfig : getJobs()) {
            jobConfig.encryptSecureProperties(preprocessedConfig, pipelineTemplateConfig);
        }
    }

    @Override
    public void validate(ValidationContext validationContext) {
        isNameValid();
    }

    private boolean isNameValid() {
        if (!new NameTypeValidator().isNameValid(name)) {
            this.errors.add(NAME, NameTypeValidator.errorMessage("stage", name));
            return false;
        }
        return true;
    }

    public void validateNameUniqueness(Map<String, StageConfig> stageNameMap) {
        if (isNameValid()) {
            String currentName = name.toLower();
            StageConfig stageWithSameName = stageNameMap.get(currentName);
            if (stageWithSameName == null) {
                stageNameMap.put(currentName, this);
            } else {
                stageWithSameName.nameConflictError();
                this.nameConflictError();
            }
        }
    }

    @Override
    public ConfigErrors errors() {
        return errors;
    }

    @Override
    public void addError(String fieldName, String message) {
        errors.add(fieldName, message);
    }

    @Override
    public void setConfigAttributes(Object attributes) {
        setConfigAttributes(attributes, null);
    }

    public void setConfigAttributes(Object attributes, TaskFactory taskFactory) {
        if (attributes == null) {
            return;
        }
        Map attributeMap = (Map) attributes;
        if (attributeMap.containsKey(NAME)) {
            name = new CaseInsensitiveString((String) attributeMap.get(NAME));
        }
        if (attributeMap.containsKey(ARTIFACT_CLEANUP_PROHIBITED)) {
            artifactCleanupProhibited = attributeMap.get(ARTIFACT_CLEANUP_PROHIBITED).equals("1") ? true : false;
        }
        if (attributeMap.containsKey(FETCH_MATERIALS)) {
            fetchMaterials = attributeMap.get(FETCH_MATERIALS).equals("1") ? true : false;
        }
        if (attributeMap.containsKey(CLEAN_WORKING_DIR)) {
            cleanWorkingDir = attributeMap.get(CLEAN_WORKING_DIR).equals("1") ? true : false;
        }
        if (attributeMap.containsKey(APPROVAL)) {
            approval.setConfigAttributes(attributeMap.get(APPROVAL));
        }
        if (attributeMap.containsKey(JOBS)) {
            if (jobConfigs == null) {
                jobConfigs = new JobConfigs();
            }
            jobConfigs.setConfigAttributes(attributeMap.get(JOBS), taskFactory);
        }
        if (attributeMap.containsKey(ENVIRONMENT_VARIABLES)) {
            variables.setConfigAttributes(attributeMap.get(ENVIRONMENT_VARIABLES));
        }
        if (attributeMap.containsKey(SECURITY_MODE)) {
            String mode = (String) attributeMap.get(SECURITY_MODE);
            if ("define".equals(mode)) {
                approval.setOperatePermissions((List<Map<String, String>>) attributeMap.get(OPERATE_USERS), (List<Map<String, String>>) attributeMap.get(OPERATE_ROLES));
            }
            if ("inherit".equals(mode)) {
                approval.removeOperatePermissions();
            }
        }
    }

    private void nameConflictError() {
        errors.add(NAME, String.format("You have defined multiple stages called '%s'. Stage names are case-insensitive and must be unique.", name));
    }

    public JobConfigs getJobs() {
        return jobConfigs;
    }

    public void setJobs(JobConfigs jobConfigs) {
        this.jobConfigs = jobConfigs;
    }

    public List<AdminUser> getOperateUsers() {
        return getApproval().getAuthConfig().getUsers();
    }

    public List<AdminRole> getOperateRoles() {
        return getApproval().getAuthConfig().getRoles();
    }

    public boolean isArtifactCleanupProhibited() {
        return artifactCleanupProhibited;
    }

    public void setArtifactCleanupProhibited(boolean artifactCleanupProhibited) {
        this.artifactCleanupProhibited = artifactCleanupProhibited;
    }

    public void cleanupAllUsagesOfRole(Role roleToDelete) {
        this.getApproval().getAuthConfig().remove(new AdminRole(roleToDelete));
    }

    public boolean canBeOperatedBy(Role role) {
        return getOperateRoles().contains(new AdminRole(role));
    }
}
