from json import loads, load, JSONDecodeError
from pathlib import Path
import importlib.resources as pkg_resources

from jsonschema.exceptions import ValidationError
from jsonschema.validators import validate
from markdown import Markdown

import menagerie
from menagerie.Items.Managers.AbstractManager import AbstractManager
from menagerie.Items.Pages.AbstractPage import AbstractPage, pretty_title
from menagerie.Items.Pages.HTMLPage import HTMLPage
from menagerie.Items.Pages.MDPage import MDPage
from menagerie.Items.Pages.Schema.JSONSchema import JSONSchema
from menagerie.Items.Pages.Schema.XMLSchema import XMLSchema
from jinja2 import Environment, FileSystemLoader

from menagerie.utils.logger import Logger


class NavItem:

    def __init__(self, content: str | list['NavItem'], is_folder: bool, meta: dict[str, object]):
        self.is_folder = is_folder
        self.titles = []
        self.meta = meta
        if is_folder:
            self.content = []
        else:
            self.content = content

    def add_item(self, nav_item: 'NavItem') -> None:
        if self.is_folder:
            self.content.append(nav_item)

    def iter_items(self) -> None:
        for page in sorted(self.content, key=lambda p: p.meta['sort_priority'], reverse=True):
            yield page

    def setup_titles(self) -> None:
        if self.is_folder:
            self.titles = [str(p.meta['title']).lower() for p in self.content]


class PageManager(AbstractManager):
    root_dir = 'pages'
    item_types: AbstractPage = (MDPage, HTMLPage, XMLSchema, JSONSchema)
    context: dict[str, object] = {

    }

    env: Environment = None
    router: dict[str, str] = {}

    def __init__(self, site_info) -> None:
        super(PageManager, self).__init__(site_info)
        self.env = Environment(loader=FileSystemLoader(self.gen.settings['content_dir']))
        self.root_dir = Path(self.gen.settings['paths', 'pages'])
        self.group_schema = loads(pkg_resources.read_text(menagerie, 'folder_schema.json', encoding='utf-8'))

    def find(self):
        super(PageManager, self).find()

    def initialize(self):
        for item in self.items:
            item.initialize()
        self.router = {p.meta['title'].lower(): str(p.out_path.relative_to(self.gen.settings['out_dir']).as_posix()) for p in self.items}
        self.gen.shared_info['pages'] = self.items
        self.filter_md = Markdown(extensions=['extra'], output_format='html5')

    def route(self, title: str) -> str:
        return self.gen.settings['url_prefix'] + self.router.get(title.lower(), "#")

    def make_nav_item_from_page(self, page: AbstractPage) -> NavItem:
        return NavItem(self.route(page.meta['title']), False, page.meta)

    def generate_nav_items(self) -> list[NavItem]:
        root_pages = []
        groups: dict[str, NavItem] = {}
        for page in self.items:
            if page.in_path.parent.name != '':
                if page.in_path.parent.name in groups.keys():
                    groups[page.in_path.parent.name].add_item(self.make_nav_item_from_page(page))
                else:
                    group_config_path = Path(page.get_path_to_open().parent, '_folder.json')
                    group_meta = {
                        'title': pretty_title(group_config_path.parent.name),
                        'sort_priority': 30
                    }
                    if group_config_path.exists():
                        try:
                            group_config = load(group_config_path)
                            validate(group_config, self.group_schema)
                            group_meta.update(group_config)
                        except JSONDecodeError as jde:
                            Logger.log_error(f"Couldn't parse config for {group_config_path.name} : {str(jde)}")
                        except ValidationError as ve:
                            Logger.log_error(f"Invalid folder config for {group_config_path.name} : {str(ve)}")
                    new_group = NavItem([], True, group_meta)
                    new_group.add_item(self.make_nav_item_from_page(page))
                    groups[group_config_path.parent.name] = new_group
            else:
                new_page = self.make_nav_item_from_page(page)
                root_pages.append(new_page)
        for group in groups.values():
            group.setup_titles()
        return sorted(list(groups.values()) + root_pages, key=lambda n: n.meta['sort_priority'], reverse=True)

    def generate(self):
        self.gen.settings['out_dir'].mkdir(parents=True, exist_ok=True)
        filters = {
            'route': self.route,
            'static': self.gen.shared_info['static_filter'],
            'upper_first': lambda x:   x[0].upper() + x[1:],
            'simple_md': lambda md: self.filter_md.convert(md)
        }
        da_globals = {
            'nav_items': self.generate_nav_items(),
        }
        self.base_env.filters.update(filters)
        self.base_env.globals.update(da_globals)
        self.env.filters.update(self.base_env.filters)
        self.env.globals.update(self.base_env.globals)
        for item in self.items:
            item.generate()
