# Source code of the Ubuntu-it website
# Copyright (C) 2023 shadMod <m.allegro@shadmod.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.


import os
import json
import locale
import shutil
import markdown

from datetime import datetime
from string import digits, ascii_letters
from random import choices as rdm_choices

try:
    # check and set with it_IT language
    if 'it_it' in locale.locale_alias:
        locale.setlocale(locale.LC_TIME, "it_IT.UTF-8")
except Exception as ex:
    print(ex)  # TODO: replaced print with logger.warning(ex)


class GetNews:
    """
    :param base_path:   data path

    Directory route path
    news > category > year > month > nr_news.md

    Sample file 000.md
    # Lorem Ipsum                       => title of newsletter, if not present takes
                                            the file progressive and year

    shadmod - m.allegro@shadmod.com     => author and mail of the same

    date: GG/MM/YYYY HH:MM             => not obbligatoried - force the date visible

    - Lorem ipsum dolor sit amet.       => body of newsletter
    - Aliquam tincidunt mauris.
    """

    def __init__(self, base_path):
        self.base_path = base_path

        self._index_news = {}
        self._category = []
        self._year = []
        self._list_files = []

    def init_assets_news(self, src_path: str, dst_path: str) -> None:
        """
        Method to rm all news in data directory and copy all news with relative indexes

        :param src_path:    source path
        :param dst_path:    destination path

        :return:            None
        :rtype:             NoneType
        """

        # rm all files and clean news dir
        for filename in os.listdir(dst_path):
            os.remove(os.path.join(dst_path, filename))

        # populate news assets
        data = []
        for category in sorted(os.listdir(src_path), reverse=True):
            category_dir = os.path.join(src_path, category)
            for year in os.listdir(category_dir):
                year_dir = os.path.join(category_dir, year)
                for month in os.listdir(year_dir):
                    month_dir = os.path.join(year_dir, month)
                    for nr_file in os.listdir(month_dir):
                        pathfile = os.path.join(month_dir, nr_file)
                        birth_time, last_edit = self.filename_time(pathfile)
                        filename = self.random_filename()
                        data.append(
                            {
                                "category": category,
                                "year": year,
                                "month": month,
                                "nr_file": nr_file.replace(".md", ""),
                                "filename": filename,
                                "birth_time": birth_time,
                                "last_edit": last_edit,
                            }
                        )
                        shutil.copy(pathfile, os.path.join(dst_path, filename))

        with open(os.path.join(dst_path, "indexes.json"), "w") as fn:
            fn.write(json.dumps(data))

    @property
    def index_news(self) -> list:
        """
        Property to get indexes.json
        """
        if not self._index_news:
            with open(os.path.join(self.base_path, "indexes.json")) as indexes:
                self._index_news = sorted(json.load(indexes), key=lambda x: x['year'], reverse=True)
        return self._index_news

    @property
    def category(self) -> list:
        """
        Property to get all category
        """
        if not self._category:
            self._category = self.get_list_data("category")
        return self._category

    @property
    def years(self) -> list:
        """
        Property to get all years
        """
        if not self._year:
            self._year = self.get_list_data("year")
        return self._year

    @property
    def list_year_month(self) -> list:
        data = []
        for val in self.index_news:
            year_month = f"{val['month']} {val['year']}"
            if year_month not in data:
                data.append(year_month)
        return data

    @staticmethod
    def random_filename() -> str:
        filename = ''.join(rdm_choices(ascii_letters + digits, k=20))
        return filename

    @staticmethod
    def filename_time(pathfile: str, fmt=None) -> (str, str):
        if not fmt:
            fmt = "%d %B %Y - %H:%M"

        # TODO: st_birthtime not exist, so I use st_mtime
        # _birth_time = os.stat(pathfile).st_birthtime
        _birth_time = os.stat(pathfile).st_mtime
        _birth_time = datetime.fromtimestamp(_birth_time).strftime(fmt)

        _last_edit = os.stat(pathfile).st_mtime
        _last_edit = datetime.fromtimestamp(_last_edit).strftime(fmt)
        return _birth_time, _last_edit

    @staticmethod
    def clean_html(value: str, html_tag: str) -> str:
        for _vl in ["", "/"]:
            value = value.replace(f"<{_vl}{html_tag}>", "")
        return value

    def get_list_data(self, key: str) -> list:
        data = []
        for val in self.index_news:
            if val[key] not in data:
                data.append(val[key])
        return data

    def filter_index_news(self, year: str, category: str, nr_file: str = None, month: str = None) -> list:
        """
        Method to filter assets news

        :param year:        year
        :param category:    category
        :param nr_file:     nr_file
        :param month:       month
        """

        raw = self.index_news
        if year:
            raw = list(filter(lambda d: d['year'] == year, raw))
        if month:
            raw = list(filter(lambda d: d['month'] == month, raw))
        if category:
            raw = list(filter(lambda d: d['category'] == category, raw))
        if nr_file:
            raw = list(filter(lambda d: d['nr_file'] == nr_file, raw))
        return raw

    def list_news(self, year: str, category: str, month: str = None) -> list:
        """
        Method to get a list of news (with filters)

        :param year:        year
        :param category:    category
        :param month:       month
        """

        # clean list_files
        self._list_files = []

        data = self.filter_index_news(year, category, month=month)

        # populate _list_files
        key_data = ["title", "author", "mail", "html"]
        for val in data:
            data = self.render_data(val["filename"], val["nr_file"], val["year"])
            for key in key_data:
                val[key] = data.get(key, "")
            if "birth_time" in data:
                val["birth_time"] = data["birth_time"]
            self._list_files.append(val)
        return self._list_files

    def get_news(self, nr_file: str, year: str, category: str) -> dict:
        """
        Method to get determinate news

        :param nr_file:     nr_file
        :param year:        year
        :param category:    category
        :return:            [
                                {
                                    "title": "Lorem Ipsum",
                                    "author": "shadmod",
                                    "mail": "m.allegro@shadmod.com",
                                    "html": "html"
                                }
                            ]
        :rtype:             list
        """

        val = self.filter_index_news(year, category, nr_file)

        if len(val) != 1:
            raise ValueError("Errore nell'univocità del numero file")
        else:
            val = val[0]

        data = self.render_data(val["filename"], val["nr_file"], val["year"])
        key_data = ["title", "author", "mail", "html"]
        for key in key_data:
            val[key] = data.get(key, "")
        if "birth_time" in data:
            val["birth_time"] = data["birth_time"]
        return val

    def render_data(self, filename: str, nr_file: str, year: str, force_date: bool = True) -> dict:
        """
        Method to render markdown file in html

        :param filename:    the name of the file
        :param nr_file:     the number of the file (000)
        :param year:        the year of the directory in which the file is located
        :param force_date:  if it's True force 'birth_time' value with the one in the file.md

        :return:    {
                        "title": "Lorem Ipsum",
                        "author": "shadmod",
                        "mail": "m.allegro@shadmod.com",
                        "birth_time": "birth_time"
                        "html": "html"
                    }
        :rtype:     dict
        """
        # init empty data
        data = {}
        # get complete path
        path = os.path.join(self.base_path, filename)

        # compile markdown in html
        with open(path, 'r') as f:
            tempHtml = markdown.markdown(f.read())
        # get header and put in 'title' key
        html_tmp = tempHtml.split("\n")
        if "<h1>" in html_tmp[0]:
            data["title"] = self.clean_html(html_tmp.pop(0), "h1")
        else:
            data["title"] = f"{year}.{nr_file}"
        # get signature with author and mail and put in relative key
        if "<p>" in html_tmp[0]:
            data["author"], data["mail"] = self.clean_html(html_tmp.pop(0), "p").split(" - ")
        # get datetime and put in relative key if force_date is True
        if "<p>" in html_tmp[0] and "<p>date:" == html_tmp[0][:8]:
            birth_time = html_tmp.pop(0)
            if force_date:
                birth_time = self.clean_html(birth_time, "p").replace("date: ", "")
                birth_time = datetime.strptime(birth_time, "%d/%m/%Y %H:%M")
                data["birth_time"] = birth_time.strftime("%d %B %Y - %H:%M")
        # what's left I put in html
        data["html"] = "".join(html_tmp)
        return data

    def news_max(self, counter: int) -> list:
        """
        Method used on the Home Page to have a maximum number of items displayed

        :param counter:     counter
        :return:            a list of news
        :rtype:             list
        """

        return self.index_news[:counter]
