/*
 * SPDX-License-Identifier: Apache-2.0
 *
 * The OpenSearch Contributors require contributions made to
 * this file be licensed under the Apache-2.0 license or a
 * compatible open source license.
 */

/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch licenses this file to you 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.
 */
/*
 * Modifications Copyright OpenSearch Contributors. See
 * GitHub history for details.
 */

package org.opensearch.gradle.testfixtures;

import org.gradle.api.GradleException;
import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.Project;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

public class TestFixtureExtension {

    private final Project project;
    final NamedDomainObjectContainer<Project> fixtures;
    final Map<String, String> serviceToProjectUseMap = new HashMap<>();

    public TestFixtureExtension(Project project) {
        this.project = project;
        this.fixtures = project.container(Project.class);
    }

    public void useFixture() {
        useFixture(this.project.getPath());
    }

    public void useFixture(String path) {
        addFixtureProject(path);
        serviceToProjectUseMap.put(path, this.project.getPath());
    }

    public void useFixture(String path, String serviceName) {
        addFixtureProject(path);
        String key = getServiceNameKey(path, serviceName);
        serviceToProjectUseMap.put(key, this.project.getPath());

        Optional<String> otherProject = this.findOtherProjectUsingService(key);
        if (otherProject.isPresent()) {
            String exceptionMessage = String.format(
                "Projects %s and %s both claim the %s service defined in the docker-compose.yml of %s. This is not supported because it "
                    + "breaks running in parallel. Configure dedicated services for each project and use those instead.",
                otherProject.get(),
                this.project.getPath(),
                serviceName,
                path
            );

            throw new GradleException(exceptionMessage);
        }
    }

    private String getServiceNameKey(String fixtureProjectPath, String serviceName) {
        return fixtureProjectPath + "::" + serviceName;
    }

    private Optional<String> findOtherProjectUsingService(String serviceName) {
        return this.project.getRootProject()
            .getAllprojects()
            .stream()
            .filter(p -> p.equals(this.project) == false)
            .filter(p -> p.getExtensions().findByType(TestFixtureExtension.class) != null)
            .map(project -> project.getExtensions().getByType(TestFixtureExtension.class))
            .flatMap(ext -> ext.serviceToProjectUseMap.entrySet().stream())
            .filter(entry -> entry.getKey().equals(serviceName))
            .map(Map.Entry::getValue)
            .findAny();
    }

    private void addFixtureProject(String path) {
        Project fixtureProject = this.project.findProject(path);
        if (fixtureProject == null) {
            throw new IllegalArgumentException("Could not find test fixture " + fixtureProject);
        }
        if (fixtureProject.file(TestFixturesPlugin.DOCKER_COMPOSE_YML).exists() == false) {
            throw new IllegalArgumentException(
                "Project " + path + " is not a valid test fixture: missing " + TestFixturesPlugin.DOCKER_COMPOSE_YML
            );
        }
        fixtures.add(fixtureProject);
        // Check for exclusive access
        Optional<String> otherProject = this.findOtherProjectUsingService(path);
        if (otherProject.isPresent()) {
            final String exceptionMessage = String.format(
                "Projects %s and %s both claim all services from %s. This is not supported because it breaks running in parallel. "
                    + "Configure specific services in docker-compose.yml for each and add the service name to `useFixture`",
                otherProject.get(),
                this.project.getPath(),
                path
            );

            throw new GradleException(exceptionMessage);
        }
    }

    boolean isServiceRequired(String serviceName, String fixtureProject) {
        if (serviceToProjectUseMap.containsKey(fixtureProject)) {
            return true;
        }
        return serviceToProjectUseMap.containsKey(getServiceNameKey(fixtureProject, serviceName));
    }
}
