import json
import logging
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple

import xmltodict
from genson import SchemaBuilder
from jsonpath_ng import parse
from lxml import etree

from arkeo.path import PathManager
from utils.file import extract_extension_from_filename
from utils.json import load_json
from utils.url import extract_domain


log = logging.getLogger(__name__)


DATA_NAME = "agencies"

FILE_LIKE = "GOVMAN"
FILE_EXT = "json"

EXT_MARKDOWN = "md"
EXT_XML = "xml"


class AgencyLoader:

    FILES: List[str] = ["GOVMAN-2025-01-13.xml", "GOVMAN-2022-12-31.xml"]
    # TODO: replace above with below // to json

    def __init__(self, path_manager: Optional[PathManager] = None):
        self.path = path_manager or PathManager()
        self.available_files = self._discover_versions()

    @property
    def dir_data(self) -> str:
        return self.path.reference + f"{DATA_NAME}/"

    def run(self, command: str, params: Optional[List[str]] = None) -> Any:
        if command == "index_hierarchy":
            return self.index_hierarchy()

        if params and isinstance(params[0], str):
            file_path = params[0]
            if not extract_extension_from_filename(file_path) == EXT_XML:
                log.warning(f"{command} requires xml file, not {file_path}")
                return
        else:
            file_path = self.dir_data + self.FILES[0]

        if command == "generate_schema":
            return self.generate_schema(file_path.replace(EXT_XML, FILE_EXT))
        elif command == "convert_with_xmltodict":
            return self.convert_with_xmltodict(file_path)
        elif command == "markdown_from_lxml":
            out_file = Path(file_path).name.replace(EXT_XML, EXT_MARKDOWN)
            return self.path.drive.write_or_false(
                f"{self.path.temp}{out_file}", self.markdown_from_lxml(file_path)
            )

        log.warning(f"{command} not available")
        return None

    def index_hierarchy(self):
        filename = f"{FILE_LIKE}.{FILE_EXT}"
        file_path = self.dir_data + filename

        gm = load_json(self.path.drive, file_path)
        if not gm:
            return

        parent_path = parse("$.GovernmentManual.Entity[*]")
        parents = parent_path.find(gm)

        entities = {}
        for parent_match in parents:
            self.index_entity(entities, parent_match.value, process_children=True)

        self.path.drive.write_or_false(
            file_path.replace(filename, f"{DATA_NAME}.{FILE_EXT}"), json.dumps(entities)
        )

    def index_entity(
        self,
        entities: Dict[str, Any],
        entity: Dict[str, Any],
        process_children: bool = False,
    ) -> Dict[int, Any]:
        if entity:
            entity_id = entity.get("@EntityId", "")
            parent_id = entity.get("@ParentId", "")
            entity_name = entity.get("AgencyName", "")
            entity_type = entity.get("Category", "")

            entity_address = entity.get("Addresses", [])
            entity_domain = self._get_domain_from_addresses(entity_address)
            domain_code = self._get_domain_code(entity_domain)

            if entity_id in entities:
                log.warning(f'[{entity_id}] exists; failed to add "{entity_name}"')
            else:
                entities[entity_id] = {
                    "code": domain_code,
                    "name": entity_name,
                    "category": entity_type,
                    "parent_id": parent_id,
                }

            if process_children:
                childrens = entity.get("Childrens", {})
                if childrens:
                    for child_level, children in childrens.items():
                        if isinstance(children, list):
                            for child in children:
                                self.index_entity(
                                    entities, child, process_children=True
                                )
                        elif isinstance(children, dict):
                            child = children
                            self.index_entity(entities, child, process_children=True)

    def generate_schema(self, file_path: str) -> None:
        """3rd party first-draft of converted xml"""
        data = load_json(self.path.drive, file_path)

        if not data:
            log.warning(f"failed to load {file_path}")
            return

        builder = SchemaBuilder()
        builder.add_object(data)
        inferred_schema = builder.to_schema()

        self.path.drive.write_or_false(
            file_path.replace(".", "-schema."),
            json.dumps(inferred_schema, indent=2, separators=(",", ":")),
        )

    def convert_with_xmltodict(self, file_path: str) -> None:
        def filter_empty_tags(
            path: Any, key: str, value: Any
        ) -> Optional[Tuple[str, Any]]:
            if value is None or value == "":
                return None
            return key, value

        with open(file_path, "r") as xml_file:
            try:
                data = xmltodict.parse(xml_file.read(), postprocessor=filter_empty_tags)
            except Exception as e:
                log.warning(f"parsing error: {e}")
                return None

        with open(file_path.replace(EXT_XML, FILE_EXT), "w") as json_file:
            json.dump(data, json_file, indent=2, separators=(",", ":"))

    def markdown_from_lxml(
        self, file_path: str, entity_id: int = 118, program_id: int = 490
    ) -> str:
        tree = etree.parse(file_path)
        root = tree.getroot()

        # all roads lead to fema
        entity = root.xpath(f'//Entity[@EntityId="{entity_id}"]')[0]
        programs = entity.xpath(".//ProgramAndActivities")[0]
        program = programs.xpath(
            f'.//ProgramAndActivity[@ProgramAndActivitiesId="{program_id}"]'
        )[0]

        markdown = f"\n{program.xpath('.//ProgramName')[0].text}"
        markdown += f"\n|domain|blurb|\n|-|-|"

        activity = program.xpath(".//Activity")[0]
        details = activity.xpath(".//Details")[0]
        for detail in details:
            footer = detail.find("FooterDetails")
            domain = extract_domain(footer.find("WebAddress").text)

            paragraph = detail.find("Paragraph").text
            paragraph = paragraph.replace("U.S.", "US").split(".")[0]
            paragraph = paragraph.replace("US ", "U.S. ").replace("US.", "U.S.")
            paragraph += "."
            markdown += f"\n|{domain}|{paragraph}|"

        return markdown

    def _discover_versions(self) -> List[str]:
        files = self.path.drive.list_files(self.dir_data, f"{FILE_LIKE}*.{EXT_XML}")
        # return sorted(files, key=self._extract_date, reverse=True)
        pass

    def _get_domain_code(self, domain: str) -> Optional[str]:
        if domain:
            return domain.split(".")[0].upper()
        return ""

    def _get_domain_from_addresses(self, addresses: Dict[str, Any]) -> Optional[str]:
        if not addresses:
            return ""

        address = addresses.get("Address", None)
        if isinstance(address, list):
            address = address[0]
        # address_id = address.get('@EntityAddressId', -1)
        footer = address.get("FooterDetails", None)
        if footer:
            return self._get_domain_from_footer(footer)
        # address = address.get('Address', '')

        return ""

    def _get_domain_from_footer(self, footer: Dict[str, Any]) -> Optional[str]:
        if footer:
            # email = footer.get('Email', '')
            domain = footer.get("WebAddress", "")
            if domain:
                domain = extract_domain(domain)
            return domain
        return ""
