import base64
import html
import json
import os
import tempfile
import time
from typing import Any
from typing import Dict
from typing import List
from typing import Optional

import PIL.Image

from ..utils import html_utils

REPORT_VERSION = 1.0


class DracReportBuilder(object):
    """Build a json and/or html file compatible with Drac dataset reports.

    Example:
        report = DracReportBuilder(
            dataset_type="Characterisation",
            dataset_title="Characterisation Results",
        )
        report.add_info("This is a demo info item.")
        report.add_table(
            title="Sample Table",
            columns=["Name", "Value"],
            data=[["Alpha", 1], ["Beta", 2]],
        )
        report.add_image("/path/to/image.png", image_title="Preview")
        report.render_json("report.json")
    """

    def __init__(self, dataset_type: str, dataset_title: str = "") -> None:
        self.image_list: Optional[Dict[str, Any]] = None
        self.dict_report: Dict[str, Any] = {}
        self.dict_report["version"] = REPORT_VERSION
        self.dict_report["type"] = dataset_type
        self.dict_report["title"] = dataset_title
        self.dict_report["items"] = []

    def set_title(self, dataset_title: str) -> None:
        self.dict_report["title"] = dataset_title

    def add_info(self, info_text: str) -> None:
        self.dict_report["items"].append({"type": "info", "value": info_text})

    def add_warning(self, info_text: str) -> None:
        self.dict_report["items"].append({"type": "warning", "value": info_text})

    def start_image_list(self) -> None:
        self.image_list = {}
        self.image_list["type"] = "images"
        self.image_list["items"] = []

    def end_image_list(self) -> None:
        self.dict_report["items"].append(self.image_list)
        self.image_list = None

    def _create_image_item(
        self,
        path_to_image: str,
        image_title: str,
        path_to_thumbnail_image: Optional[str] = None,
        thumbnail_height: Optional[int] = None,
        thumbnail_width: Optional[int] = None,
    ) -> Dict[str, Any]:
        item: Dict[str, Any] = {}
        item["type"] = "image"
        item["suffix"] = path_to_image.split(".")[-1]
        item["title"] = image_title
        with PIL.Image.open(path_to_image) as image:
            item["xsize"] = image.size[0]
            item["ysize"] = image.size[1]
        with open(path_to_image, "rb") as image_file:
            item["value"] = base64.b64encode(image_file.read()).decode("utf-8")
        if path_to_thumbnail_image is None:
            if thumbnail_height is not None and thumbnail_width is not None:
                item["thumbnailSuffix"] = path_to_image.split(".")[-1]
                item["thumbnailXsize"] = thumbnail_height
                item["thumbnailYsize"] = thumbnail_width
                with open(path_to_image, "rb") as image_file:
                    item["thumbnailValue"] = base64.b64encode(image_file.read())
        else:
            item["thumbnailSuffix"] = path_to_thumbnail_image.split(".")[-1]
            with PIL.Image.open(path_to_thumbnail_image) as thumbnail_im:
                item["thumbnailXsize"] = thumbnail_im.size[0]
                item["thumbnailYsize"] = thumbnail_im.size[1]
            with open(path_to_thumbnail_image, "rb") as thumbnail_file:
                item["thumbnailValue"] = base64.b64encode(thumbnail_file.read()).decode(
                    "utf-8"
                )
        return item

    def add_image(
        self,
        path_to_image: str,
        image_title: str = "",
        path_to_thumbnail_image: Optional[str] = None,
        thumbnail_height: Optional[int] = None,
        thumbnail_width: Optional[int] = None,
    ) -> None:
        if self.image_list is None:
            self.dict_report["items"].append(
                self._create_image_item(
                    path_to_image,
                    image_title,
                    path_to_thumbnail_image,
                    thumbnail_height,
                    thumbnail_width,
                )
            )
        else:
            self.image_list["items"].append(
                self._create_image_item(
                    path_to_image,
                    image_title,
                    path_to_thumbnail_image,
                    thumbnail_height,
                    thumbnail_width,
                )
            )

    def add_table(
        self,
        title: str,
        columns: List[str],
        data: List[List[Any]],
        orientation: str = "horizontal",
    ) -> None:
        item: Dict[str, Any] = {}
        item["type"] = "table"
        item["title"] = title
        item["columns"] = columns
        item["data"] = data
        item["orientation"] = orientation
        self.dict_report["items"].append(item)

    def add_log_file(self, title: str, link_text: str, path_to_log_file: str) -> None:
        item: Dict[str, Any] = {}
        item["type"] = "logFile"
        item["title"] = title
        item["link_text"] = link_text
        with open(path_to_log_file, "r", encoding="utf-8", errors="ignore") as log_file:
            item["logText"] = log_file.read()
        self.dict_report["items"].append(item)

    def get_dict_report(self) -> Dict[str, Any]:
        return self.dict_report

    def render_json(self, path_to_json_dir: str) -> str:
        path_to_json_file = os.path.join(path_to_json_dir, "report.json")
        with open(path_to_json_file, "w") as json_file:
            json_file.write(json.dumps(self.dict_report, indent=4))
        return path_to_json_file

    def escape_characters(self, str_value: str) -> str:
        str_value = html.escape(str_value)
        str_value = str_value.replace(str("Å"), "&Aring;")
        str_value = str_value.replace(str("°"), "&deg;")
        str_value = str_value.replace("\n", "<br>")
        return str_value

    def render_html(
        self, path_to_html_dir: str, name_of_index_file: str = "index.html"
    ) -> str:
        page = html_utils.HtmlPage(mode="loose_html")
        page.init(
            title=self.dict_report["title"], footer="Generated on %s" % time.asctime()
        )
        page.div(align_="LEFT")
        page.h1()
        page.strong(self.dict_report["title"])
        page.h1.close()
        page.div.close()
        for item in self.dict_report["items"]:
            if "value" in item:
                item_value = item["value"]
            if "title" in item:
                item_title = self.escape_characters(item["title"])
            if item["type"] == "info":
                page.p(item_value)
            if item["type"] == "warning":
                page.font(_color="red", size="+1")
                page.p()
                page.strong(item_value)
                page.p.close()
                page.font.close()
            elif item["type"] == "image":
                self._render_image(page, item, path_to_html_dir)
                page.br()
                page.p(item_title)
            elif item["type"] == "images":
                page.table()
                page.tr(align_="CENTER")
                for item in item["items"]:
                    item_title = self.escape_characters(item["title"])
                    page.td()
                    page.table()
                    page.tr()
                    page.td()
                    self._render_image(page, item, path_to_html_dir)
                    page.td.close()
                    page.tr.close()
                    page.tr(align_="CENTER")
                    page.td(item_title)
                    page.tr.close()
                    page.table.close()
                    page.td.close()
                page.tr.close()
                page.table.close()
                page.br()
            elif item["type"] == "table":
                title_bg_colour = "#99c2ff"
                column_title_bg_colour = "#ffffff"
                data_bg_colour = "#e6f0ff"
                page.table(
                    border_="1",
                    cellpadding_="2",
                    width="100%",
                    style_=(
                        "border: 1px solid black; border-collapse: collapse; margin:"
                        " 0px; font-size: 12px"
                    ),
                )
                page.tr(align_="LEFT")
                # page.strong(item_title)
                page.th(
                    item_title,
                    bgcolor_=title_bg_colour,
                    align_="LEFT",
                    style_=(
                        "border: 1px solid black; border-collapse: collapse; padding:"
                        " 5px;"
                    ),
                    colspan_=str(len(item["columns"])),
                )
                page.tr.close()
                if "orientation" in item and item["orientation"] == "vertical":
                    for index1 in range(len(item["columns"])):
                        item_column = self.escape_characters(item["columns"][index1])
                        page.tr(align_="LEFT")
                        page.th(
                            item_column,
                            bgcolor_=column_title_bg_colour,
                            align_="LEFT",
                            style_=(
                                "border: 1px solid black; border-collapse: collapse;"
                                " padding: 5px;"
                            ),
                        )
                        for index2 in range(len(item["data"])):
                            item_data = self.escape_characters(
                                str(item["data"][index2][index1])
                            )
                            page.th(
                                item_data,
                                bgcolor_=data_bg_colour,
                                style_="padding: 5px;",
                            )
                        page.tr.close()
                else:
                    page.tr(
                        align_="LEFT",
                        bgcolor_=column_title_bg_colour,
                        style_=(
                            "border: 1px solid black; border-collapse: collapse;"
                            " padding: 5px;"
                        ),
                    )
                    for column in item["columns"]:
                        item_column = self.escape_characters(column)
                        page.th(
                            item_column,
                            style_=(
                                "border: 1px solid black; border-collapse: collapse;"
                                " padding: 5px;"
                            ),
                        )
                    page.tr.close()
                    for list_row in item["data"]:
                        page.tr(
                            align_="LEFT",
                            bgcolor_=data_bg_colour,
                            style_="border-collapse: collapse; padding: 5px;",
                        )
                        for cell in list_row:
                            item_cell = self.escape_characters(str(cell))
                            page.th(
                                item_cell,
                                style_=(
                                    "border: 1px solid black; border-collapse:"
                                    " collapse; padding: 5px;"
                                ),
                            )
                        page.tr.close()
                page.table.close()
                page.br()
            elif item["type"] == "logFile":
                path_to_log_html = os.path.join(path_to_html_dir, item_title + ".html")
                if os.path.exists(path_to_log_html):
                    fd, path_to_log_html = tempfile.mkstemp(
                        suffix=".html",
                        prefix=item_title.replace(" ", "_") + "_",
                        dir=path_to_html_dir,
                    )
                    os.close(fd)
                page_log_html = html_utils.HtmlPage()
                page_log_html.h1(item_title)
                page_log_html.pre(page.escape(item["logText"]))
                with open(path_to_log_html, "w") as log_html_file:
                    log_html_file.write(str(page_log_html))
                os.chmod(path_to_log_html, 0o644)
                page.p()
                page.a(item["link_text"], href_=os.path.basename(path_to_log_html))
                page.p.close()
        html = str(page)
        page_path = os.path.join(path_to_html_dir, name_of_index_file)
        with open(page_path, "w") as file_page:
            file_page.write(html)
        return page_path

    def _render_image(
        self, page: Any, item: Dict[str, Any], path_to_html_dir: str
    ) -> None:
        image_name = item["title"].replace(" ", "_")
        path_to_image = os.path.join(
            path_to_html_dir, "{0}.{1}".format(image_name, item["suffix"])
        )
        if os.path.exists(path_to_image):
            fd, path_to_image = tempfile.mkstemp(
                suffix="." + item["suffix"],
                prefix=image_name + "_",
                dir=path_to_html_dir,
            )
            os.close(fd)
        with open(path_to_image, "wb") as image_file:
            image_file.write(base64.b64decode(item["value"]))
        os.chmod(path_to_image, 0o644)
        if "thumbnailValue" in item:
            thumbnail_image_name = image_name + "_thumbnail"
            path_to_thumbnail_image = os.path.join(
                path_to_html_dir, "{0}.{1}".format(thumbnail_image_name, item["suffix"])
            )
            if os.path.exists(path_to_thumbnail_image):
                fd, path_to_thumbnail_image = tempfile.mkstemp(
                    suffix="." + item["suffix"],
                    prefix=thumbnail_image_name + "_",
                    dir=path_to_html_dir,
                )
                os.close(fd)
            with open(path_to_thumbnail_image, "wb") as thumbnail_file:
                thumbnail_file.write(base64.b64decode(item["thumbnailValue"]))
            os.chmod(path_to_thumbnail_image, 0o644)
            page_reference_image = html_utils.HtmlPage(mode="loose_html")
            page_reference_image.init(
                title=image_name, footer="Generated on %s" % time.asctime()
            )
            page_reference_image.h1(image_name)
            page_reference_image.br()
            page_reference_image.img(
                src=os.path.basename(path_to_image),
                title=image_name,
                width=item["xsize"],
                height=item["ysize"],
            )
            page_reference_image.br()
            path_page_reference_image = os.path.join(
                path_to_html_dir, "{0}.html".format(image_name)
            )
            with open(path_page_reference_image, "w") as file_page:
                file_page.write(str(page_reference_image))
            page.a(href=os.path.basename(path_page_reference_image))
            page.img(
                src=os.path.basename(path_to_thumbnail_image),
                title=image_name,
                width=item["thumbnailXsize"],
                height=item["thumbnailYsize"],
            )
            page.a.close()
        else:
            page.img(
                src=os.path.basename(path_to_image),
                title=item["title"],
                width=item["xsize"],
                height=item["ysize"],
            )
