import json
import logging
import math
from datetime import date
from datetime import datetime
from typing import Iterator
from typing import Union

import inflection
from patent_client import session
from patent_client.util.manager import Manager

from .model import USApplication
from .schema import USApplicationSchema

logger = logging.getLogger(__name__)


class HttpException(Exception):
    pass


class NotAvailableException(Exception):
    pass


QUERY_FIELDS = "appEarlyPubNumber applId appLocation appType appStatus_txt appConfrNumber appCustNumber appGrpArtNumber appCls appSubCls appEntityStatus_txt patentNumber patentTitle primaryInventor firstNamedApplicant appExamName appExamPrefrdName appAttrDockNumber appPCTNumber appIntlPubNumber wipoEarlyPubNumber pctAppType firstInventorFile appClsSubCls rankAndInventorsList"


class USApplicationManager(Manager[USApplication]):
    primary_key = "appl_id"
    query_url = "https://ped.uspto.gov/api/queries"
    page_size = 20
    __schema__ = USApplicationSchema()

    def __init__(self, *args, **kwargs):
        super(USApplicationManager, self).__init__(*args, **kwargs)
        self.pages = dict()

    def __len__(self):
        max_length = self.get_page(0)["numFound"] - self.config["offset"]
        limit = self.config["limit"]
        if not limit:
            return max_length
        else:
            return limit if limit < max_length else max_length

    def _get_results(self) -> Iterator[USApplication]:
        num_pages = math.ceil(len(self) / self.page_size)
        page_num = 0
        counter = 0
        while page_num < num_pages:
            page_data = self.get_page(page_num)
            for item in page_data["docs"]:
                if not self.config["limit"] or counter < self.config["limit"]:
                    yield self.__schema__.load(item)
                counter += 1
            page_num += 1

    def __iter__(self) -> Iterator[USApplication]:
        return super(USApplicationManager, self).__iter__()

    def get_page(self, page_number):
        if page_number not in self.pages:
            query_params = self.query_params(page_number)
            response = session.post(self.query_url, json=query_params, timeout=10)
            if not response.ok:
                if self.is_online():
                    raise HttpException(
                        f"{response.status_code}\n{response.text}\n{response.headers}\n{json.dumps(query_params)}"
                    )
            data = response.json()
            self.pages[page_number] = data["queryResults"]["searchResponse"]["response"]
        return self.pages[page_number]

    def query_params(self, page_no):
        sort_query = ""
        for s in self.config["order_by"]:
            if s[0] == "-":
                sort_query += f"{inflection.camelize(s[1:], uppercase_first_letter=False)} desc ".strip()
            else:
                sort_query += (
                    f"{inflection.camelize(s, uppercase_first_letter=False)} asc"
                ).strip()
        if not sort_query:
            sort_query = None

        query = list()
        mm_active = True
        for k, v in self.config["filter"].items():
            field = inflection.camelize(k, uppercase_first_letter=False)
            if not v:
                continue
            elif type(v) in (list, tuple):
                body = f" OR ".join(
                    f'"{value}"' if " " in value else value for value in v
                )
                mm_active = False
            else:
                body = v
            query.append(f"{field}:({body})")

        mm = "100%" if "appEarlyPubNumber" not in query else "90%"

        query = {
            "qf": QUERY_FIELDS,
            "fl": "*",  # ",".join(inflection.camelize(f, uppercase_first_letter=False) for f in RETURN_FIELDS),#"*",
            "searchText": " AND ".join(query).strip(),
            "sort": sort_query,
            "facet": "false",
            "mm": mm,
            "start": page_no * self.page_size + self.config["offset"],
            # "rows": self.page_size,
        }
        if not mm_active:
            del query["mm"]
        return query

    @property
    def allowed_filters(self):
        fields = self.fields()
        return list(fields.keys())

    def fields(self):
        if not hasattr(self.__class__, "_fields"):
            url = "https://ped.uspto.gov/api/search-fields"
            response = session.get(url)
            if not response.ok:
                raise ValueError("Can't get fields!")
            raw = response.json()
            output = {inflection.underscore(key): value for (key, value) in raw.items()}
            self.__class__._fields = output
        return self.__class__._fields

    def is_online(self):
        with session.cache_disabled():
            response = session.get("https://ped.uspto.gov/api/search-fields")
            if response.ok:
                return True
            elif "requested resource is not available" in response.text:
                raise NotAvailableException(
                    "Patent Examination Data is Offline - this is a USPTO problem"
                )
            elif "attempt failed or the origin closed the connection" in response.text:
                raise NotAvailableException(
                    "The Patent Examination Data API is Broken! - this is a USPTO problem"
                )
            else:
                raise NotAvailableException("There is a USPTO problem")

    @property
    def query_fields(self):
        fields = self.fields()
        for k in sorted(fields.keys()):
            if "facet" in k:
                continue
            print(f"{k} ({fields[k]})")


class DateEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, date):
            return o.isoformat()

        return json.JSONEncoder.default(self, o)
