/*
 * The MIT License
 * 
 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
 * 
 * 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 hudson.search;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;

import hudson.model.FreeStyleProject;
import hudson.model.ListView;

import java.io.IOException;
import java.net.URL;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import hudson.model.User;
import hudson.model.View;
import hudson.security.ACL;
import hudson.security.ACLContext;
import hudson.security.AuthorizationStrategy;
import hudson.security.GlobalMatrixAuthorizationStrategy;
import jenkins.model.Jenkins;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import net.sf.json.JSONSerializer;

import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.JenkinsRule.WebClient;
import org.jvnet.hudson.test.MockAuthorizationStrategy;
import org.jvnet.hudson.test.MockFolder;

import com.gargoylesoftware.htmlunit.AlertHandler;
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.Page;

/**
 * @author Kohsuke Kawaguchi
 */
public class SearchTest {
  
    @Rule public JenkinsRule j = new JenkinsRule();
    
    /**
     * No exact match should result in a failure status code.
     */
    @Test
    public void testFailure() throws Exception {
        try {
            j.search("no-such-thing");
            fail("404 expected");
        } catch (FailingHttpStatusCodeException e) {
            assertEquals(404,e.getResponse().getStatusCode());
        }
    }

    /**
     * Makes sure the script doesn't execute.
     */
    @Issue("JENKINS-3415")
    @Test
    public void testXSS() throws Exception {
        try {
            WebClient wc = j.createWebClient();
            wc.setAlertHandler(new AlertHandler() {
                public void handleAlert(Page page, String message) {
                    throw new AssertionError();
                }
            });
            wc.search("<script>alert('script');</script>");
            fail("404 expected");
        } catch (FailingHttpStatusCodeException e) {
            assertEquals(404,e.getResponse().getStatusCode());
        }
    }
    
    @Test
    public void testSearchByProjectName() throws Exception {
        final String projectName = "testSearchByProjectName";
        
        j.createFreeStyleProject(projectName);
        
        Page result = j.search(projectName);
        assertNotNull(result);
        j.assertGoodStatus(result);
        
        // make sure we've fetched the testSearchByDisplayName project page
        String contents = result.getWebResponse().getContentAsString();
        assertTrue(contents.contains(String.format("<title>%s [Jenkins]</title>", projectName)));
    }

    @Issue("JENKINS-24433")
    @Test
    public void testSearchByProjectNameBehindAFolder() throws Exception {
        FreeStyleProject myFreeStyleProject = j.createFreeStyleProject("testSearchByProjectName");
        MockFolder myMockFolder = j.createFolder("my-folder-1");

        Page result = j.createWebClient().goTo(myMockFolder.getUrl() + "search?q="+ myFreeStyleProject.getName());

        assertNotNull(result);
        j.assertGoodStatus(result);

        URL resultUrl = result.getUrl();
        assertTrue(resultUrl.toString().equals(j.getInstance().getRootUrl() + myFreeStyleProject.getUrl()));
    }

    @Issue("JENKINS-24433")
    @Test
    public void testSearchByProjectNameInAFolder() throws Exception {

        MockFolder myMockFolder = j.createFolder("my-folder-1");
        FreeStyleProject myFreeStyleProject = myMockFolder.createProject(FreeStyleProject.class, "my-job-1");

        Page result = j.createWebClient().goTo(myMockFolder.getUrl() + "search?q=" + myFreeStyleProject.getFullName());

        assertNotNull(result);
        j.assertGoodStatus(result);

        URL resultUrl = result.getUrl();
        assertTrue(resultUrl.toString().equals(j.getInstance().getRootUrl() + myFreeStyleProject.getUrl()));
    }

    @Test
    public void testSearchByDisplayName() throws Exception {
        final String displayName = "displayName9999999";
        
        FreeStyleProject project = j.createFreeStyleProject("testSearchByDisplayName");
        project.setDisplayName(displayName);
        
        Page result = j.search(displayName);
        assertNotNull(result);
        j.assertGoodStatus(result);
        
        // make sure we've fetched the testSearchByDisplayName project page
        String contents = result.getWebResponse().getContentAsString();
        assertTrue(contents.contains(String.format("<title>%s [Jenkins]</title>", displayName)));
    }
    
    @Test
    public void testSearch2ProjectsWithSameDisplayName() throws Exception {
        // create 2 freestyle projects with the same display name
        final String projectName1 = "projectName1";
        final String projectName2 = "projectName2";
        final String projectName3 = "projectName3";
        final String displayName = "displayNameFoo";
        final String otherDisplayName = "otherDisplayName";
        
        FreeStyleProject project1 = j.createFreeStyleProject(projectName1);
        project1.setDisplayName(displayName);
        FreeStyleProject project2 = j.createFreeStyleProject(projectName2);
        project2.setDisplayName(displayName);
        FreeStyleProject project3 = j.createFreeStyleProject(projectName3);
        project3.setDisplayName(otherDisplayName);

        // make sure that on search we get back one of the projects, it doesn't
        // matter which one as long as the one that is returned has displayName
        // as the display name
        Page result = j.search(displayName);
        assertNotNull(result);
        j.assertGoodStatus(result);

        // make sure we've fetched the testSearchByDisplayName project page
        String contents = result.getWebResponse().getContentAsString();
        assertTrue(contents.contains(String.format("<title>%s [Jenkins]</title>", displayName)));
        assertFalse(contents.contains(otherDisplayName));
    }
    
    @Test
    public void testProjectNamePrecedesDisplayName() throws Exception {
        final String project1Name = "foo";
        final String project1DisplayName = "project1DisplayName";
        final String project2Name = "project2Name";
        final String project2DisplayName = project1Name;
        final String project3Name = "project3Name";
        final String project3DisplayName = "project3DisplayName";
        
        // create 1 freestyle project with the name foo
        FreeStyleProject project1 = j.createFreeStyleProject(project1Name);
        project1.setDisplayName(project1DisplayName);
        
        // create another with the display name foo
        FreeStyleProject project2 = j.createFreeStyleProject(project2Name);
        project2.setDisplayName(project2DisplayName);

        // create a third project and make sure it's not picked up by search
        FreeStyleProject project3 = j.createFreeStyleProject(project3Name);
        project3.setDisplayName(project3DisplayName);
        
        // search for foo
        Page result = j.search(project1Name);
        assertNotNull(result);
        j.assertGoodStatus(result);
        
        // make sure we get the project with the name foo
        String contents = result.getWebResponse().getContentAsString();
        assertTrue(contents.contains(String.format("<title>%s [Jenkins]</title>", project1DisplayName)));
        // make sure projects 2 and 3 were not picked up
        assertFalse(contents.contains(project2Name));
        assertFalse(contents.contains(project3Name));
        assertFalse(contents.contains(project3DisplayName));
    }
    
    @Test
    public void testGetSuggestionsHasBothNamesAndDisplayNames() throws Exception {
        final String projectName = "project name";
        final String displayName = "display name";

        FreeStyleProject project1 = j.createFreeStyleProject(projectName);
        project1.setDisplayName(displayName);
        
        WebClient wc = j.createWebClient();
        Page result = wc.goTo("search/suggest?query=name", "application/json");
        assertNotNull(result);
        j.assertGoodStatus(result);
        
        String content = result.getWebResponse().getContentAsString();
        System.out.println(content);
        JSONObject jsonContent = (JSONObject)JSONSerializer.toJSON(content);
        assertNotNull(jsonContent);
        JSONArray jsonArray = jsonContent.getJSONArray("suggestions");
        assertNotNull(jsonArray);
        
        assertEquals(2, jsonArray.size());
        
        boolean foundProjectName = false;
        boolean foundDisplayName = false;
        for(Object suggestion : jsonArray) {
            JSONObject jsonSuggestion = (JSONObject)suggestion;
            
            String name = (String)jsonSuggestion.get("name");
            if(projectName.equals(name)) {
                foundProjectName = true;
            }
            else if(displayName.equals(name)) {
                foundDisplayName = true;
            }
        }

        assertTrue(foundProjectName);
        assertTrue(foundDisplayName);
    }

    @Issue("JENKINS-24433")
    @Test
    public void testProjectNameBehindAFolderDisplayName() throws Exception {
        final String projectName1 = "job-1";
        final String displayName1 = "job-1 display";

        final String projectName2 = "job-2";
        final String displayName2 = "job-2 display";

        FreeStyleProject project1 = j.createFreeStyleProject(projectName1);
        project1.setDisplayName(displayName1);

        MockFolder myMockFolder = j.createFolder("my-folder-1");

        FreeStyleProject project2 = myMockFolder.createProject(FreeStyleProject.class, projectName2);
        project2.setDisplayName(displayName2);

        WebClient wc = j.createWebClient();
        Page result = wc.goTo(myMockFolder.getUrl() + "search/suggest?query=" + projectName1, "application/json");
        assertNotNull(result);
        j.assertGoodStatus(result);

        String content = result.getWebResponse().getContentAsString();
        JSONObject jsonContent = (JSONObject)JSONSerializer.toJSON(content);
        assertNotNull(jsonContent);
        JSONArray jsonArray = jsonContent.getJSONArray("suggestions");
        assertNotNull(jsonArray);

        assertEquals(2, jsonArray.size());

        boolean foundDisplayName = false;
        for(Object suggestion : jsonArray) {
            JSONObject jsonSuggestion = (JSONObject)suggestion;

            String name = (String)jsonSuggestion.get("name");
            if(projectName1.equals(name)) {
                foundDisplayName = true;
            }
        }

        assertTrue(foundDisplayName);
    }

    @Issue("JENKINS-24433")
    @Test
    public void testProjectNameInAFolderDisplayName() throws Exception {
        final String projectName1 = "job-1";
        final String displayName1 = "job-1 display";

        final String projectName2 = "job-2";
        final String displayName2 = "my-folder-1 job-2";

        FreeStyleProject project1 = j.createFreeStyleProject(projectName1);
        project1.setDisplayName(displayName1);

        MockFolder myMockFolder = j.createFolder("my-folder-1");

        FreeStyleProject project2 = myMockFolder.createProject(FreeStyleProject.class, projectName2);
        project2.setDisplayName(displayName2);

        WebClient wc = j.createWebClient();
        Page result = wc.goTo(myMockFolder.getUrl() + "search/suggest?query=" + projectName2, "application/json");
        assertNotNull(result);
        j.assertGoodStatus(result);

        String content = result.getWebResponse().getContentAsString();
        JSONObject jsonContent = (JSONObject)JSONSerializer.toJSON(content);
        assertNotNull(jsonContent);
        JSONArray jsonArray = jsonContent.getJSONArray("suggestions");
        assertNotNull(jsonArray);

        assertEquals(1, jsonArray.size());

        boolean foundDisplayName = false;
        for(Object suggestion : jsonArray) {
            JSONObject jsonSuggestion = (JSONObject)suggestion;

            String name = (String)jsonSuggestion.get("name");

            if(displayName2.equals(name)) {
                foundDisplayName = true;
            }
        }

        assertTrue(foundDisplayName);
    }

    /**
     * Disable/enable status shouldn't affect the search
     */
    @Issue("JENKINS-13148")
    @Test
    public void testDisabledJobShouldBeSearchable() throws Exception {
        FreeStyleProject p = j.createFreeStyleProject("foo-bar");
        assertTrue(suggest(j.jenkins.getSearchIndex(), "foo").contains(p));

        p.disable();
        assertTrue(suggest(j.jenkins.getSearchIndex(), "foo").contains(p));
    }

    /**
     * All top-level jobs should be searchable, not just jobs in the current view.
     */
    @Issue("JENKINS-13148")
    @Test
    public void testCompletionOutsideView() throws Exception {
        FreeStyleProject p = j.createFreeStyleProject("foo-bar");
        ListView v = new ListView("empty1",j.jenkins);
        ListView w = new ListView("empty2",j.jenkins);
        j.jenkins.addView(v);
        j.jenkins.addView(w);
        j.jenkins.setPrimaryView(w);

        // new view should be empty
        assertFalse(v.contains(p));
        assertFalse(w.contains(p));
        assertFalse(j.jenkins.getPrimaryView().contains(p));

        assertTrue(suggest(j.jenkins.getSearchIndex(),"foo").contains(p));
    }

    @Issue("SECURITY-385")
    @Test
    public void testInaccessibleViews() throws IOException {
        j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
        GlobalMatrixAuthorizationStrategy strategy = new GlobalMatrixAuthorizationStrategy();
        strategy.add(Jenkins.READ, "alice");
        j.jenkins.setAuthorizationStrategy(strategy);

        j.jenkins.addView(new ListView("foo", j.jenkins));

        // SYSTEM can see all the views
        assertEquals("two views exist", 2, Jenkins.getInstance().getViews().size());
        List<SearchItem> results = new ArrayList<>();
        j.jenkins.getSearchIndex().suggest("foo", results);
        assertEquals("nonempty results list", 1, results.size());


        // Alice can't
        assertFalse("no permission", j.jenkins.getView("foo").getACL().hasPermission(User.get("alice").impersonate(), View.READ));
        ACL.impersonate(User.get("alice").impersonate(), new Runnable() {
            @Override
            public void run() {
                assertEquals("no visible views", 0, Jenkins.getInstance().getViews().size());

                List<SearchItem> results = new ArrayList<>();
                j.jenkins.getSearchIndex().suggest("foo", results);
                assertEquals("empty results list", Collections.emptyList(), results);
            }
        });
    }
    
    @Test
    public void testSearchWithinFolders() throws Exception {
        MockFolder folder1 = j.createFolder("folder1");
        FreeStyleProject p1 = folder1.createProject(FreeStyleProject.class, "myjob");
        MockFolder folder2 = j.createFolder("folder2");
        FreeStyleProject p2 = folder2.createProject(FreeStyleProject.class, "myjob");
        List<SearchItem> suggest = suggest(j.jenkins.getSearchIndex(), "myjob");
        assertTrue(suggest.contains(p1));
        assertTrue(suggest.contains(p2));
    }


    @Test
    @Issue("JENKINS-7874")
    public void adminOnlyLinksNotShownToRegularUser() {
        j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
        MockAuthorizationStrategy mas = new MockAuthorizationStrategy();
        mas.grant(Jenkins.READ).onRoot().toEveryone();
        j.jenkins.setAuthorizationStrategy(mas);

        try(ACLContext _ = ACL.as(User.get("alice"))) {
            List<SearchItem> results = new ArrayList<>();
            j.jenkins.getSearchIndex().find("config", results);
            j.jenkins.getSearchIndex().find("manage", results);
            j.jenkins.getSearchIndex().find("log", results);
            assertEquals("empty results list", 0, results.size());
        }
    }

    private List<SearchItem> suggest(SearchIndex index, String term) {
        List<SearchItem> result = new ArrayList<SearchItem>();
        index.suggest(term, result);
        return result;
    }
}
