import re

from ..lib.judge_parsers import normalize_judge_string
from ..lib.log_tools import make_default_logger
from ..lib.string_utils import (
    clean_string,
    convert_date_string,
    force_unicode,
    harmonize,
)
from ..lib.utils import clean_court_object, previous_and_next
from .docket_report import DocketReport
from .utils import (
    get_nonce_from_form,
    get_pacer_doc_id_from_doc1_url,
    get_pacer_seq_no_from_doc1_anchor,
)

logger = make_default_logger()

date_regex = r"[—\d\-–/]*"


class DocketHistoryReport(DocketReport):
    assigned_to_regex = r"(.*),\s+presiding"
    referred_to_regex = r"(.*),\s+referral"
    date_filed_regex = re.compile(r"[fF]iled:\s+(%s)" % date_regex)
    date_last_filing_regex = re.compile(
        r"last\s+filing:\s+(%s)" % date_regex, flags=re.IGNORECASE
    )
    date_filed_and_entered_regex = re.compile(
        r"& Entered:\s+(%s)" % date_regex
    )
    date_entered_regex = re.compile(r"[eE]ntered:\s+(%s)" % date_regex)

    PATH = "cgi-bin/HistDocQry.pl"

    @property
    def data(self):
        """Get all the data back from this endpoint."""
        if self.is_valid is False:
            return {}
        data = self.metadata.copy()
        data["docket_entries"] = self.docket_entries
        return data

    @property
    def metadata(self):
        if self.is_valid is False:
            return {}

        if self._metadata is not None:
            return self._metadata

        self._set_metadata_values()
        data = {
            "court_id": self.court_id,
            "docket_number": self._get_docket_number(),
            "case_name": self._get_case_name(),
            "date_filed": self._get_value(
                self.date_filed_regex, self.metadata_values, cast_to_date=True
            ),
            "date_last_filing": self._get_value(
                self.date_last_filing_regex,
                self.metadata_values,
                cast_to_date=True,
            ),
            "date_terminated": self._get_value(
                self.date_terminated_regex,
                self.metadata_values,
                cast_to_date=True,
            ),
            "date_discharged": self._get_value(
                self.date_discharged_regex,
                self.metadata_values,
                cast_to_date=True,
            ),
            "assigned_to_str": self._get_assigned_judge(),
            "referred_to_str": self._get_judge(self.referred_to_regex),
        }

        data = clean_court_object(data)
        self._metadata = data
        return data

    def query(
        self,
        pacer_case_id,
        query_type="History",
        order_by="asc",
        show_de_descriptions=False,
    ):
        """Query the docket history report and return the results. Because of
        the way this works, you have to hit PACER twice. Once to get a nonce,
        and a second time to make your query.

        :param pacer_case_id: The internal PACER case ID for a case.
        :param query_type: The type of query placed. Either "History" or
        "Documents".
        :param show_de_descriptions: Whether to show docket entry descriptions
        in the report.
        :param order_by: The ordering desired for the results, either 'asc' or
        'desc'.
        :return: request response object
        """
        # Set up and sanity tests
        assert (
            self.session is not None
        ), "session attribute of DocketHistoryReport cannot be None."

        if query_type not in ["History", "Documents"]:
            raise ValueError("Invalid value for 'query_type' parameter.")
        if (
            show_de_descriptions is not True
            and show_de_descriptions is not False
        ):
            raise ValueError("")
        if order_by not in ["asc", "desc"]:
            raise ValueError("Invalid value for 'order_by' parameter.")

        logger.info(
            "Getting nonce for docket history report with "
            "pacer_case_id: %s" % pacer_case_id
        )
        r = self.session.get(f"{self.url}?{pacer_case_id}")
        nonce = get_nonce_from_form(r)

        query_params = {
            "QueryType": query_type,
            "sort1": order_by,
        }
        if show_de_descriptions:
            query_params["DisplayDktText"] = "DisplayDktText"

        logger.info(
            "Querying docket history report for case ID '%s' with "
            "params %s and nonce %s" % (pacer_case_id, query_params, nonce)
        )

        self.response = self.session.post(
            f"{self.url}?{nonce}", data=query_params
        )
        self.parse()

    @property
    def docket_entries(self):
        if self._docket_entries is not None:
            return self._docket_entries

        docket_header = './/th/text()[contains(., "Description")]'
        docket_entry_rows = self.tree.xpath(f"//table[{docket_header}]//tr")[
            1:
        ]  # Skip first row

        docket_entries = []
        for row in docket_entry_rows:
            cells = row.xpath("./td")
            if len(cells) == 3:
                # Normal row, parse the document_number, date, etc.
                de = {}
                de["document_number"] = clean_string(cells[0].text_content())
                if de["document_number"] == "":
                    de["document_number"] = None
                anchors = cells[0].xpath(".//a")
                if len(anchors) == 1:
                    doc1_url = anchors[0].xpath("./@href")[0]
                    de["pacer_doc_id"] = get_pacer_doc_id_from_doc1_url(
                        doc1_url
                    )
                    de["pacer_seq_no"] = get_pacer_seq_no_from_doc1_anchor(
                        anchors[0]
                    )
                else:
                    # Unlinked minute entry; may or may not be numbered
                    de["pacer_doc_id"] = None
                    de["pacer_seq_no"] = None
                de["date_filed"] = self._get_date_filed(cells[1])
                de["date_entered"] = self._get_date_entered(cells[1])
                de["short_description"] = force_unicode(
                    cells[2].text_content()
                )
                de["description"] = ""
                docket_entries.append(de)
            elif len(cells) == 1:
                # Document long description. Get it, and add it to previous de.
                desc = force_unicode(cells[0].text_content())
                label = "Docket Text: "
                if desc.startswith(label):
                    desc = desc[len(label) :]
                docket_entries[-1]["description"] = desc

        # Some docket history entries show the word "doc" instead of an entry
        # number. These items aren't on the docket itself, and so for now we
        # just skip them.
        docket_entries = [
            de
            for de in docket_entries
            if de["document_number"] is None or de["document_number"].isdigit()
        ]
        docket_entries = clean_court_object(docket_entries)
        self._docket_entries = docket_entries
        return docket_entries

    def _get_date_filed(self, cell):
        s = clean_string(cell.text_content())
        regexes = [self.date_filed_regex, self.date_filed_and_entered_regex]
        for regex in regexes:
            m = regex.search(s)
            if m:
                return convert_date_string(m.group(1))

    def _get_date_entered(self, cell):
        s = clean_string(cell.text_content())
        regexes = [self.date_entered_regex, self.date_filed_and_entered_regex]
        for regex in regexes:
            m = regex.search(s)
            if m:
                return convert_date_string(m.group(1))

    def _set_metadata_values(self):
        text_nodes = self.tree.xpath("//center[not(.//table)]//text()")
        values = []
        for s in text_nodes:
            s = clean_string(force_unicode(s))
            if s:
                values.append(s)
        values.append(" ".join(values))
        self.metadata_values = values

    def _get_case_name(self):
        if self.is_bankruptcy:
            # Uses both b/c sometimes the bankr. cases have a dist-style docket
            # number.
            regexes = [
                self.docket_number_dist_regex,
                self.docket_number_bankr_regex,
            ]
        else:
            regexes = [self.docket_number_dist_regex]

        matches = set()
        # Skip the last value, it's a concat of all previous values and
        # isn't needed for case name matching.
        for prev, v, nxt in previous_and_next(self.metadata_values[:-1]):
            if prev is None:
                continue
            for regex in regexes:
                match = regex.search(prev)
                if match:
                    if self.is_bankruptcy:
                        return harmonize(v)
                    for cn_regex in self.case_name_regexes:
                        cn_match = cn_regex.match(v)
                        if cn_match:
                            matches.add(cn_match.group(1))
        if len(matches) == 1:
            case_name = list(matches)[0]
        else:
            case_name = "Unknown Case Title"
        return harmonize(case_name)

    def _get_docket_number(self):
        if self.is_bankruptcy:
            # Uses both b/c sometimes the bankr. cases have a dist-style docket
            # number.
            regexes = [
                self.docket_number_dist_regex,
                self.docket_number_bankr_regex,
            ]
        else:
            regexes = [self.docket_number_dist_regex]
        nodes = self.tree.xpath('//center//font[@size="+1"]')
        string_nodes = [s.text_content() for s in nodes]
        for regex in regexes:
            for s in string_nodes:
                match = regex.search(s)
                if match:
                    return match.group(1)

    def _get_assigned_judge(self):
        if self.is_bankruptcy:
            # Look for string like "Judge: Michael J. Fox"
            for prev, v, nxt in previous_and_next(self.metadata_values[:-1]):
                if prev is not None and re.search("Judge:", prev, flags=re.I):
                    return normalize_judge_string(v)[0]
        else:
            # Look for string like "Michael J. Fox, presiding"
            return self._get_judge(self.assigned_to_regex)
