///////////////////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
// Copyright (C) 2001-2023 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
///////////////////////////////////////////////////////////////////////////////////////////////

package com.puppycrawl.tools.checkstyle.filters;

import static com.google.common.truth.Truth.assertWithMessage;
import static com.puppycrawl.tools.checkstyle.checks.coding.IllegalTokenTextCheck.MSG_KEY;
import static com.puppycrawl.tools.checkstyle.checks.naming.AbstractNameCheck.MSG_INVALID_PATTERN;

import java.util.Collections;
import java.util.Set;

import org.junit.jupiter.api.Test;

import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
import com.puppycrawl.tools.checkstyle.checks.coding.IllegalTokenTextCheck;
import com.puppycrawl.tools.checkstyle.checks.naming.ConstantNameCheck;
import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
import nl.jqno.equalsverifier.EqualsVerifier;
import nl.jqno.equalsverifier.EqualsVerifierReport;
import nl.jqno.equalsverifier.Warning;

public class SuppressionXpathFilterTest extends AbstractModuleTestSupport {

    private static final String PATTERN = "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$";

    private static final String[] ALL_MESSAGES = {
        "20:29: " + getCheckMessage(ConstantNameCheck.class,
                                    MSG_INVALID_PATTERN, "bad_name", PATTERN),
    };

    @Override
    protected String getPackageLocation() {
        return "com/puppycrawl/tools/checkstyle/filters/suppressionxpathfilter";
    }

    @Test
    public void testAcceptOne() throws Exception {
        final String[] suppressed = CommonUtil.EMPTY_STRING_ARRAY;
        verifyFilterWithInlineConfigParser(getPath("InputSuppressionXpathFilterAcceptOne.java"),
                                           ALL_MESSAGES,
                                           removeSuppressed(ALL_MESSAGES, suppressed));
    }

    @Test
    public void testAcceptTwo() throws Exception {
        final String[] expected = {
            "20:29: " + getCheckMessage(ConstantNameCheck.class, MSG_INVALID_PATTERN,
                                        "different_name_than_suppression", PATTERN),
        };
        final String[] suppressed = CommonUtil.EMPTY_STRING_ARRAY;
        verifyFilterWithInlineConfigParser(getPath("InputSuppressionXpathFilterAcceptTwo.java"),
                                           expected, removeSuppressed(expected, suppressed));
    }

    @Test
    public void testAcceptOnNullFile() throws Exception {
        final String[] suppressed = CommonUtil.EMPTY_STRING_ARRAY;
        verifyFilterWithInlineConfigParser(
            getPath("InputSuppressionXpathFilterAcceptOnNullFile.java"), ALL_MESSAGES,
            removeSuppressed(ALL_MESSAGES, suppressed));
    }

    @Test
    public void testNonExistentSuppressionFileWithFalseOptional() throws Exception {
        final String fileName = getPath("non_existent_suppression_file.xml");
        try {
            final boolean optional = false;
            createSuppressionXpathFilter(fileName, optional);
            assertWithMessage("Exception is expected").fail();
        }
        catch (CheckstyleException ex) {
            assertWithMessage("Invalid error message")
                .that(ex.getMessage())
                .isEqualTo("Unable to find: " + fileName);
        }
    }

    @Test
    public void testExistingInvalidSuppressionFileWithTrueOptional() throws Exception {
        final String fileName = getPath("InputSuppressionXpathFilterInvalidFile.xml");
        try {
            final boolean optional = true;
            createSuppressionXpathFilter(fileName, optional);
            assertWithMessage("Exception is expected").fail();
        }
        catch (CheckstyleException ex) {
            assertWithMessage("Invalid error message")
                .that(ex.getMessage())
                .isEqualTo("Unable to parse " + fileName
                    + " - invalid files or checks or message format for suppress-xpath");
        }
    }

    @Test
    public void testExistingSuppressionFileWithTrueOptional() throws Exception {
        final String[] suppressed = CommonUtil.EMPTY_STRING_ARRAY;
        verifyFilterWithInlineConfigParser(
            getPath("InputSuppressionXpathFilterAcceptWithOptionalTrue.java"), ALL_MESSAGES,
            removeSuppressed(ALL_MESSAGES, suppressed));
    }

    @Test
    public void testNonExistentSuppressionFileWithTrueOptional() throws Exception {
        final String[] suppressed = CommonUtil.EMPTY_STRING_ARRAY;
        verifyFilterWithInlineConfigParser(
            getPath("InputSuppressionXpathFilterNonExistentFileWithTrueOptional.java"),
            ALL_MESSAGES, removeSuppressed(ALL_MESSAGES, suppressed));
    }

    @Test
    public void testReject() throws Exception {
        final String[] suppressed = {
            "20:29: " + getCheckMessage(ConstantNameCheck.class,
                                        MSG_INVALID_PATTERN, "bad_name", PATTERN),
        };
        verifyFilterWithInlineConfigParser(getPath("InputSuppressionXpathFilterReject.java"),
                                           ALL_MESSAGES,
                                           removeSuppressed(ALL_MESSAGES, suppressed));
    }

    @Test
    public void testEqualsAndHashCode() {
        final EqualsVerifierReport ev = EqualsVerifier.forClass(SuppressionXpathFilter.class)
                .usingGetClass()
                .withIgnoredFields("file", "optional", "configuration")
                .suppress(Warning.NONFINAL_FIELDS).report();
        assertWithMessage("Error: " + ev.getMessage())
                .that(ev.isSuccessful())
                .isTrue();
    }

    @Test
    public void testExternalResource() throws Exception {
        final boolean optional = false;
        final String fileName = getPath("InputSuppressionXpathFilterIdAndQuery.xml");
        final SuppressionXpathFilter filter = createSuppressionXpathFilter(fileName, optional);
        final Set<String> expected = Collections.singleton(fileName);
        final Set<String> actual = filter.getExternalResourceLocations();
        assertWithMessage("Invalid external resource")
            .that(actual)
            .isEqualTo(expected);
    }

    private static SuppressionXpathFilter createSuppressionXpathFilter(String fileName,
                                                                       boolean optional)
            throws CheckstyleException {
        final SuppressionXpathFilter suppressionXpathFilter = new SuppressionXpathFilter();
        suppressionXpathFilter.setFile(fileName);
        suppressionXpathFilter.setOptional(optional);
        suppressionXpathFilter.finishLocalSetup();
        return suppressionXpathFilter;
    }

    @Test
    public void testFalseEncodeString() throws Exception {
        final String pattern = "[^a-zA-z0-9]*";
        final String[] expected = {
            "17:24: " + getCheckMessage(IllegalTokenTextCheck.class, MSG_KEY, pattern),
            "19:23: " + getCheckMessage(IllegalTokenTextCheck.class, MSG_KEY, pattern),
            "21:28: " + getCheckMessage(IllegalTokenTextCheck.class, MSG_KEY, pattern),
            "23:26: " + getCheckMessage(IllegalTokenTextCheck.class, MSG_KEY, pattern),
            "25:26: " + getCheckMessage(IllegalTokenTextCheck.class, MSG_KEY, pattern),
            "27:26: " + getCheckMessage(IllegalTokenTextCheck.class, MSG_KEY, pattern),
            "29:23: " + getCheckMessage(IllegalTokenTextCheck.class, MSG_KEY, pattern),
            "31:29: " + getCheckMessage(IllegalTokenTextCheck.class, MSG_KEY, pattern),
            "33:29: " + getCheckMessage(IllegalTokenTextCheck.class, MSG_KEY, pattern),
        };

        final String[] suppressed = {
            "17:24: " + getCheckMessage(IllegalTokenTextCheck.class, MSG_KEY, pattern),
            "19:23: " + getCheckMessage(IllegalTokenTextCheck.class, MSG_KEY, pattern),
            "21:28: " + getCheckMessage(IllegalTokenTextCheck.class, MSG_KEY, pattern),
            "23:26: " + getCheckMessage(IllegalTokenTextCheck.class, MSG_KEY, pattern),
            "25:26: " + getCheckMessage(IllegalTokenTextCheck.class, MSG_KEY, pattern),
            "27:26: " + getCheckMessage(IllegalTokenTextCheck.class, MSG_KEY, pattern),
            "29:23: " + getCheckMessage(IllegalTokenTextCheck.class, MSG_KEY, pattern),
        };

        verifyFilterWithInlineConfigParser(getPath("InputSuppressionXpathFilterEscapeString.java"),
                                           expected, removeSuppressed(expected, suppressed));
    }

    @Test
    public void testFalseEncodeChar() throws Exception {
        final String pattern = "[^a-zA-z0-9]*";
        final String[] expected = {
            "17:14: " + getCheckMessage(IllegalTokenTextCheck.class, MSG_KEY, pattern),
            "19:14: " + getCheckMessage(IllegalTokenTextCheck.class, MSG_KEY, pattern),
            "21:14: " + getCheckMessage(IllegalTokenTextCheck.class, MSG_KEY, pattern),
            "23:14: " + getCheckMessage(IllegalTokenTextCheck.class, MSG_KEY, pattern),
            "25:14: " + getCheckMessage(IllegalTokenTextCheck.class, MSG_KEY, pattern),
        };

        final String[] suppressed = {
            "21:14: " + getCheckMessage(IllegalTokenTextCheck.class, MSG_KEY, pattern),
            "23:14: " + getCheckMessage(IllegalTokenTextCheck.class, MSG_KEY, pattern),
            "25:14: " + getCheckMessage(IllegalTokenTextCheck.class, MSG_KEY, pattern),
        };

        verifyFilterWithInlineConfigParser(getPath("InputSuppressionXpathFilterEscapeChar.java"),
                                           expected, removeSuppressed(expected, suppressed));
    }

    @Test
    public void testXpathSuppression() throws Exception {
        for (int test = 1; test <= 4; test++) {

            final String[] expected = {
                "20:29: " + getCheckMessage(ConstantNameCheck.class, MSG_INVALID_PATTERN,
                        "different_name_than_suppression", PATTERN),
            };
            final String[] suppressed = CommonUtil.EMPTY_STRING_ARRAY;
            final String path = "InputSuppressionXpathFilter" + test + ".java";
            verifyFilterWithInlineConfigParser(getPath(path),
                    expected, removeSuppressed(expected, suppressed));
        }
    }
}
