"""
@relation(SDOC-SRS-134, scope=file)
"""

import os
from pathlib import Path
from typing import Dict, List

import xlsxwriter
from xlsxwriter.workbook import Workbook
from xlsxwriter.worksheet import Worksheet

from strictdoc.backend.sdoc.models.document import SDocDocument
from strictdoc.backend.sdoc.models.grammar_element import ReferenceType
from strictdoc.backend.sdoc.models.model import (
    SDocIteratedElementIF,
)
from strictdoc.backend.sdoc.models.node import SDocNode
from strictdoc.backend.sdoc.models.reference import (
    FileReference,
    ParentReqReference,
)
from strictdoc.core.project_config import ProjectConfig
from strictdoc.core.traceability_index import TraceabilityIndex
from strictdoc.helpers.cast import assert_cast

EXCEL_SHEET_NAME = "Requirements"
MAX_WIDTH = 75
HEADER_MARGIN = 3
MAX_WIDTH_KEY = "max_width"
COLUMN_HEADER_KEY = "header"
PARENT_COLUMN_HEADER_LABEL = "PARENT"
EXPORT_COLUMNS = [
    {
        "name": "uid",
        "header": "UID",
    },
    {
        "name": "statement",
        "header": "STATEMENT",
    },
]


class ExcelGenerator:
    @staticmethod
    def export_tree(
        traceability_index: TraceabilityIndex,
        output_excel_root: str,
        project_config: ProjectConfig,
    ) -> None:
        Path(output_excel_root).mkdir(parents=True, exist_ok=True)

        document: SDocDocument
        for document in traceability_index.document_tree.document_list:
            assert document.meta is not None

            document_out_file_name = (
                f"{document.meta.document_filename_base}.xlsx"
            )
            document_out_file = os.path.join(
                output_excel_root, document_out_file_name
            )

            ExcelGenerator._export_single_document(
                document, traceability_index, document_out_file, project_config
            )

    @staticmethod
    def _export_single_document(
        document: SDocDocument,
        traceability_index: TraceabilityIndex,
        document_out_file: str,
        project_config: ProjectConfig,
    ) -> None:
        with xlsxwriter.Workbook(document_out_file) as workbook:
            worksheet = workbook.add_worksheet(name=EXCEL_SHEET_NAME)
            workbook.set_properties(
                {
                    "title": project_config.project_title,
                    "comments": "Created with StrictDoc.",
                }
            )

            # Header row.
            row = 1

            fields = project_config.excel_export_fields

            # FIXME: Check if all fields are defined by the DocumentGrammar.
            assert fields is not None
            columns = ExcelGenerator._init_columns_width(fields)

            document_iterator = traceability_index.get_document_iterator(
                document
            )
            all_nodes = list(
                map(
                    lambda node_with_context_: node_with_context_[0],
                    document_iterator.all_content(print_fragments=False),
                )
            )
            req_uid_rows = ExcelGenerator._lookup_refs(all_nodes)

            if len(req_uid_rows):
                for node, _ in traceability_index.get_document_iterator(
                    document
                ).all_content(print_fragments=False):
                    if (
                        not isinstance(node, SDocNode)
                        or node.reserved_uid is None
                    ):
                        # Only export the requirements with UID.
                        continue

                    for idx, field in enumerate(fields, start=0):
                        field_uc = field.upper()

                        # Special treatment for ParentReqReference and Comments.
                        if field_uc in (
                            "RELATIONS:PARENT",
                            "PARENT",
                            "PARENTS",
                        ):
                            parent_refs = node.get_requirement_references(
                                ReferenceType.PARENT
                            )
                            if len(parent_refs) > 0:
                                # FIXME: Allow multiple parent refs.
                                ref = assert_cast(
                                    parent_refs[0], ParentReqReference
                                )
                                columns[field][MAX_WIDTH_KEY] = max(
                                    len(ref.ref_uid),
                                    columns[field][MAX_WIDTH_KEY],
                                )
                                if ref.ref_uid in req_uid_rows:
                                    worksheet.write_url(
                                        row,
                                        idx,
                                        (
                                            "internal:"
                                            f"'{EXCEL_SHEET_NAME}'"
                                            f"!A{req_uid_rows[ref.ref_uid]}"
                                        ),
                                        string=ref.ref_uid,
                                    )
                                else:
                                    worksheet.write(row, idx, ref.ref_uid)
                        elif field_uc in ("COMMENT", "COMMENTS"):
                            # Using a transition marker to separate multiple
                            # comments.
                            comment_fields = node.get_comment_fields()
                            if len(comment_fields) > 0:
                                comment_row_value: str = ""
                                for comment_field_ in comment_fields:
                                    if len(comment_row_value) > 0:
                                        comment_row_value += "\n----------\n"
                                    comment_row_value += (
                                        comment_field_.get_text_value()
                                    )
                                worksheet.write(row, idx, comment_row_value)
                                if (
                                    comment_row_value
                                    and len(comment_row_value)
                                    > columns[field][MAX_WIDTH_KEY]
                                ):
                                    columns[field][MAX_WIDTH_KEY] = len(
                                        comment_row_value
                                    )
                        elif field_uc == "RELATIONS":
                            if len(node.relations) > 0:
                                relations_components = []
                                # Using a transition marker to separate
                                # multiple references.
                                for relation_ in node.relations:
                                    if isinstance(
                                        relation_, ParentReqReference
                                    ):
                                        relations_components.append(
                                            relation_.ref_type
                                            + ": "
                                            + relation_.ref_uid
                                        )
                                    elif isinstance(relation_, FileReference):
                                        relations_components.append(
                                            relation_.ref_type
                                            + ": "
                                            + relation_.get_posix_path()
                                        )
                                relations_row_value: str = (
                                    "\n----------\n".join(relations_components)
                                )
                                worksheet.write(row, idx, relations_row_value)
                                value_len = len(relations_row_value)
                                columns[field][MAX_WIDTH_KEY] = max(
                                    value_len, columns[field][MAX_WIDTH_KEY]
                                )
                        elif field_uc in node.ordered_fields_lookup.keys():
                            req_field = node.ordered_fields_lookup[field_uc][0]
                            value: str = req_field.get_text_value()
                            worksheet.write(row, idx, value)
                            value_len = len(value)
                            columns[field][MAX_WIDTH_KEY] = max(
                                value_len, columns[field][MAX_WIDTH_KEY]
                            )
                        elif hasattr(node, "special_fields"):
                            if len(node.special_fields):
                                for special_field in node.special_fields:
                                    if (
                                        special_field.field_name.upper()
                                        == field_uc
                                    ):
                                        value = special_field.field_value
                                        worksheet.write(row, idx, value)
                                        if (
                                            value
                                            and len(value)
                                            > columns[field][MAX_WIDTH_KEY]
                                        ):
                                            columns[field][MAX_WIDTH_KEY] = len(
                                                value
                                            )
                                        break

                    row += 1

                # Add a table around all this data, allowing filtering and
                # ordering in Excel.
                worksheet.add_table(
                    0,
                    0,
                    row - 1,
                    len(fields) - 1,
                    {"columns": ExcelGenerator._init_headers(fields)},
                )

                # Enforce columns width.
                ExcelGenerator._set_columns_width(
                    workbook, worksheet, columns, fields
                )
            else:
                # No requirement with UID.
                print(  # noqa: T201
                    "No requirement with UID, nothing to export into excel"
                )

        if row == 1:
            os.unlink(document_out_file)

    @staticmethod
    def _lookup_refs(
        document_contents: List[SDocIteratedElementIF],
    ) -> Dict[str, int]:
        refs: Dict[str, int] = {}
        row = 1

        for content_node in document_contents:
            if isinstance(content_node, SDocNode):
                if content_node.reserved_uid:
                    # Only export the requirements with uid, allowing tracking.
                    row += 1
                    refs[content_node.reserved_uid] = row

        return refs

    @staticmethod
    def _init_columns_width(fields: List[str]) -> Dict[str, Dict[str, int]]:
        columns: Dict[str, Dict[str, int]] = {}

        for field in fields:
            columns[field] = {}
            columns[field][MAX_WIDTH_KEY] = len(field) + HEADER_MARGIN
        return columns

    @staticmethod
    def _set_columns_width(
        workbook: Workbook,
        worksheet: Worksheet,
        columns: Dict[str, Dict[str, int]],
        fields: List[str],
    ) -> None:
        cell_format_text_wrap = workbook.add_format()
        cell_format_text_wrap.set_text_wrap()

        for idx, field in enumerate(fields, start=0):
            if columns[field][MAX_WIDTH_KEY] > MAX_WIDTH:
                worksheet.set_column(idx, idx, MAX_WIDTH, cell_format_text_wrap)
            else:
                worksheet.set_column(idx, idx, columns[field][MAX_WIDTH_KEY])

    @staticmethod
    def _init_headers(fields: List[str]) -> List[Dict[str, str]]:
        headers: List[Dict[str, str]] = []

        for field in fields:
            headers.append({"header": field.upper()})

        return headers
