package com.ibm.icu.dev.test;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * “Known issues” manager. Intended to be shared between ICU, CLDR, &c. Test frameworks can create
 * an instance of this to manage known issues
 */
public class UnicodeKnownIssues {
    private Map<String, List<String>> knownIssues = new TreeMap<>();
    /**
     * Max number of lines to show by default (including the "more") unless -allKnownIssues is
     * given. Must be at least 2.
     */
    public static final int KNOWN_ISSUES_CURTAILMENT = 2;

    /** true if all issues should be shown, false if they should be curtailed. */
    private boolean allKnownIssues;

    /**
     * Construct a known issue manager
     *
     * @param allKnownIssues true if all known issues should be printed, not curtailed
     */
    public UnicodeKnownIssues(boolean allKnownIssues) {
        this.allKnownIssues = allKnownIssues;
    }

    /** Base URL for browsing Unicode JIRA */
    public static final String UNICODE_JIRA_BROWSE = "https://unicode-org.atlassian.net/browse/";

    static final Pattern ICU_TICKET_PATTERN = Pattern.compile("(?i)(?:icu-)?(\\d+)");
    static final Pattern CLDR_TICKET_PATTERN = Pattern.compile("(?i)cldr(?:bug:|-)?(\\d+)");

    /**
     * Match all linkable ticket patterns
     *
     * @see {org.unicode.cldr.util.CLDRURLS#CLDR_TICKET_BROWSE}
     */
    static final Pattern UNICODE_JIRA_PATTERN = Pattern.compile("(CLDR|ICU)-(\\d+)");

    /**
     * Log the known issue. Call this from the test framework when logKnownIssue() is called.
     *
     * @param path Path to the error, will be returned in the known issue list
     * @param ticket A ticket number string. For an ICU ticket, use "ICU-10245". For a CLDR ticket,
     *     use "CLDR-12345". For compatibility, "1234" -> ICU-1234 and "cldrbug:456" -> CLDR-456
     * @param comment Additional comment, or null
     */
    public void logKnownIssue(String path, String ticket, String comment) {
        StringBuilder descBuf = new StringBuilder(path);

        if (comment != null && comment.length() > 0) {
            descBuf.append(" (" + comment + ")");
        }
        String description = descBuf.toString();

        String ticketLink = "Unknown Ticket";
        if (ticket != null && ticket.length() > 0) {
            Matcher matcher = ICU_TICKET_PATTERN.matcher(ticket);
            if (matcher.matches()) {
                ticketLink = "ICU-" + matcher.group(1);
            } else {
                matcher = CLDR_TICKET_PATTERN.matcher(ticket);
                if (matcher.matches()) {
                    ticketLink = "CLDR-" + matcher.group(1);
                }
            }
        }

        List<String> lines = knownIssues.get(ticketLink);
        if (lines == null) {
            lines = new ArrayList<>();
            knownIssues.put(ticketLink, lines);
        }
        if (!lines.contains(description)) {
            lines.add(description);
        }
    }

    /**
     * Print out all known issues to the logFn. Usage: printKnownIssues(System.out::println)
     *
     * @param logFn consumer for Strings (e.g. System.out::println)
     * @return true if (!allKnownIssues) and we had to curtail
     */
    boolean printKnownIssues(Consumer<String> logFn) {
        if (knownIssues.isEmpty()) {
            return false;
        }
        boolean didCurtail = false;
        logFn.accept("\n " + knownIssues.size() + " Known Issues:");
        for (Entry<String, List<String>> entry : knownIssues.entrySet()) {
            String ticketLink = entry.getKey();
            if (UNICODE_JIRA_PATTERN.matcher(ticketLink) != null) {
                logFn.accept(ticketLink + " <" + UNICODE_JIRA_BROWSE + ticketLink + ">");
            } else {
                // Unknown or something else
                logFn.accept("<" + ticketLink + ">");
            }
            List<String> entries = entry.getValue();
            int issuesToShow = entries.size();
            if (!allKnownIssues && issuesToShow > KNOWN_ISSUES_CURTAILMENT) {
                issuesToShow = (KNOWN_ISSUES_CURTAILMENT - 1);
            }
            for (int i = 0; i < issuesToShow; i++) {
                logFn.accept("  - " + entries.get(i));
            }
            if (entries.size() > issuesToShow) {
                didCurtail = true;
                logFn.accept("  ... and " + (entries.size() - issuesToShow) + " more");
            }
        }
        return didCurtail;
    }
}
