/*
 * The MIT License
 *
 * Copyright 2016 CloudBees, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.jenkinsci.plugins.github_branch_source;

import com.cloudbees.jenkins.GitHubRepositoryName;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.model.Item;
import java.io.IOException;
import java.io.StringReader;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import jenkins.scm.api.SCMNavigator;
import jenkins.scm.api.SCMNavigatorOwner;
import jenkins.scm.api.SCMSource;
import jenkins.scm.api.SCMSourceEvent;
import org.jenkinsci.plugins.github.extension.GHEventsSubscriber;
import org.jenkinsci.plugins.github.extension.GHSubscriberEvent;
import org.kohsuke.github.GHEvent;
import org.kohsuke.github.GHEventPayload;
import org.kohsuke.github.GitHub;

import static com.google.common.collect.Sets.immutableEnumSet;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.WARNING;
import static org.kohsuke.github.GHEvent.REPOSITORY;

/**
 * This subscriber manages {@link org.kohsuke.github.GHEvent} REPOSITORY.
 */
@Extension
public class GitHubRepositoryEventSubscriber extends GHEventsSubscriber {

    private static final Logger LOGGER = Logger.getLogger(GitHubRepositoryEventSubscriber.class.getName());
    private static final Pattern REPOSITORY_NAME_PATTERN = Pattern.compile("https?://([^/]+)/([^/]+)/([^/]+)");


    @Override
    protected boolean isApplicable(@Nullable Item item) {
        if (item instanceof SCMNavigatorOwner) {
            for (SCMNavigator navigator : ((SCMNavigatorOwner) item).getSCMNavigators()) {
                if (navigator instanceof GitHubSCMNavigator) {
                    return true; // TODO allow navigators to opt-out
                }
            }
        }
        return false;
    }

    /**
     * @return set with only REPOSITORY event
     */
    @Override
    protected Set<GHEvent> events() {
        return immutableEnumSet(REPOSITORY);
    }

    @Override
    protected void onEvent(GHSubscriberEvent event) {
        try {
            final GHEventPayload.Repository p = GitHub.offline()
                    .parseEventPayload(new StringReader(event.getPayload()), GHEventPayload.Repository.class);
            String action = p.getAction();
            String repoUrl = p.getRepository().getHtmlUrl().toExternalForm();
            LOGGER.log(Level.FINE, "Received {0} for {1} from {2}",
                    new Object[]{event.getGHEvent(), repoUrl, event.getOrigin()}
            );
            boolean fork = p.getRepository().isFork();
            Matcher matcher = REPOSITORY_NAME_PATTERN.matcher(repoUrl);
            if (matcher.matches()) {
                final GitHubRepositoryName repo = GitHubRepositoryName.create(repoUrl);
                if (repo == null) {
                    LOGGER.log(WARNING, "Malformed repository URL {0}", repoUrl);
                    return;
                }
                if (!"created".equals(action)) {
                    LOGGER.log(FINE, "Repository {0} was {1} not created, will be ignored",
                            new Object[]{repo.getRepositoryName(), action});
                    return;
                }
                if (!fork) {
                    LOGGER.log(FINE, "Repository {0} was created but it is empty, will be ignored",
                            repo.getRepositoryName());
                    return;
                }
                final NewSCMSourceEvent e = new NewSCMSourceEvent(event.getTimestamp(), event.getOrigin(), p, repo);
                // Delaying the indexing for some seconds to avoid GitHub cache
                SCMSourceEvent.fireLater(e, GitHubSCMSource.getEventDelaySeconds(), TimeUnit.SECONDS);
            } else {
                LOGGER.log(WARNING, "Malformed repository URL {0}", repoUrl);
            }
        } catch (IOException e) {
            LogRecord lr = new LogRecord(Level.WARNING, "Could not parse {0} event from {1} with payload: {2}");
            lr.setParameters(new Object[]{event.getGHEvent(), event.getOrigin(), event.getPayload()});
            lr.setThrown(e);
            LOGGER.log(lr);
        }
    }

    private static class NewSCMSourceEvent extends SCMSourceEvent<GHEventPayload.Repository> {
        private final String repoHost;
        private final String repoOwner;
        private final String repository;

        public NewSCMSourceEvent(long timestamp, String origin, GHEventPayload.Repository event,
                                 GitHubRepositoryName repo) {
            super(Type.CREATED, timestamp, event, origin);
            this.repoHost = repo.getHost();
            this.repoOwner = event.getRepository().getOwnerName();
            this.repository = event.getRepository().getName();
        }

        private boolean isApiMatch(String apiUri) {
            return repoHost.equalsIgnoreCase(RepositoryUriResolver.hostnameFromApiUri(apiUri));
        }

        @Override
        public boolean isMatch(@NonNull SCMNavigator navigator) {
            return navigator instanceof GitHubSCMNavigator
                    && isApiMatch(((GitHubSCMNavigator) navigator).getApiUri())
                    && repoOwner.equalsIgnoreCase(((GitHubSCMNavigator) navigator).getRepoOwner());
        }

        @Override
        public boolean isMatch(@NonNull SCMSource source) {
            return source instanceof GitHubSCMSource
                    && isApiMatch(((GitHubSCMSource) source).getApiUri())
                    && repoOwner.equalsIgnoreCase(((GitHubSCMSource) source).getRepoOwner())
                    && repository.equalsIgnoreCase(((GitHubSCMSource) source).getRepository());
        }

        @NonNull
        @Override
        public String getSourceName() {
            return repository;
        }
    }
}