import re

import frontmatter
from mkdocs.config import config_options
from mkdocs.config.defaults import MkDocsConfig
from mkdocs.exceptions import PluginError
from mkdocs.plugins import BasePlugin
from mkdocs.structure.files import File, Files
from mkdocs.structure.pages import Page

from mkdocs_decision_records._markdown_utils import _list, _meta_table

CONFIG_DECISIONS_FOLDER_KEY = "decisions_folder"
CONFIG_TICKET_URL_PREFIX = "ticket_url_prefix"

CONFIG_LIFECYCLE_COLORS_KEY = "lifecycle_stages"
CONFIG_DECISIONS_FOLDER_DEFAULT = "adr"

CONFIG_REQUIRED_DECIDERS_COUNT_KEY = "required_deciders_count"
CONFIG_REQUIRED_DECIDERS_COUNT_DEFAULT = 1

DR_TITLE_WITH_NUM = re.compile(r"\d{3,}.*")
DR_NUM = re.compile(r'(?:[a-zA-Z\-]+)?(\d+)')

DEFAULT_LIFECYCLE_COLORS = {
    "accepted": "#28a745",
    "proposed": "gray",
    "rejected": "#dc3545",
    "deprecated": "#6c757d",
    "superseded": "#17a2b8",
}


class InvalidMetaDataError(PluginError):
    def __init__(self, page: Page, field: str, message: str):
        self.field = field
        self.raw_message = message
        self.message = (
            f"Invalid metadata for field '{field}' in {page.file.src_path}: {message}"
        )


def _require_meta(page: Page, field: str) -> any:
    val = page.meta.get(field, None)
    if val is None:
        raise InvalidMetaDataError(page, field, "Required, but not set.")
    return val

class DecisionRecordsPlugin(BasePlugin):
    config_scheme = (
        (
            CONFIG_DECISIONS_FOLDER_KEY,
            config_options.Type(str, default=CONFIG_DECISIONS_FOLDER_DEFAULT),
        ),
        (CONFIG_TICKET_URL_PREFIX, config_options.Type(str, default=None)),
        (
            CONFIG_LIFECYCLE_COLORS_KEY,
            config_options.Type(dict, default=DEFAULT_LIFECYCLE_COLORS),
        ),
        (
            CONFIG_REQUIRED_DECIDERS_COUNT_KEY,
            config_options.Type(int, default=CONFIG_REQUIRED_DECIDERS_COUNT_DEFAULT),
        ),
    )
    _dr_page_mapping: dict[int|str, File] = {}

    def on_files(self, files: Files, /, *, config: MkDocsConfig) -> Files | None:
        docs_pages = files.documentation_pages()
        for doc in docs_pages:
            parsed_frontmatter = frontmatter.loads(doc.content_string)
            dr_id = parsed_frontmatter.get("id", None)
            if dr_id is None:
                continue
            self._dr_page_mapping[int(dr_id)] = doc

    def on_page_markdown(
        self, markdown: str, page: Page, config: MkDocsConfig, files: Files
    ):
        if not page.file.src_path.startswith(
            self.config.get(
                CONFIG_DECISIONS_FOLDER_KEY, CONFIG_DECISIONS_FOLDER_DEFAULT
            )
        ):
            return markdown

        title = page.meta.get("title", None) or page.title
        dr_id = _require_meta(page, "id")

        if dr_id == 0:
            page.title = "000 - Template"
            return markdown

        self._ensure_page_is_unique(dr_id, files, page)

        meta = [
            ("Status", self._create_status_badge(page)),
            ("Date", _require_meta(page, "date")),
        ]

        deciders = page.meta.get("deciders", [])
        if len(deciders) < self.required_deciders_count:
            raise InvalidMetaDataError(
                page,
                "deciders",
                f"At least {self.required_deciders_count} deciders are required for a decision",
            )
        elif len(deciders) > 0:
            meta.append(
                (
                    "Deciders" if len(deciders) > 1 else "Decider",
                    "\n".join(_list(deciders)) if len(deciders) > 1 else deciders[0],
                )
            )

        ticket = page.meta.get("ticket", None)
        if ticket is not None:
            meta.append(
                (
                    "Ticket",
                    self._ticket_text(ticket),
                )
            )

        status = _require_meta(page, "status")
        if status == "superseded":
            superseded_by = page.meta.get("superseded_by", None)
            if superseded_by is None:
                raise InvalidMetaDataError(
                    page,
                    "superseded_by",
                    "When setting an ADR to superseded you need to set superseded_by to an ADR id.",
                )

            superseded_by_id = superseded_by
            if  match := DR_NUM.match(str(superseded_by)):
                superseded_by_id = int(match.group(1))

            if isinstance(superseded_by, int):
                superseded_by = f"{superseded_by:03d}"

            if superseded_by_id not in self._dr_page_mapping:
                raise InvalidMetaDataError(
                    page,
                    "superseded_by",
                    "Decision records with identifier %s has not been found" % superseded_by,
                )

            meta.append(
                (
                    "Superseded by",
                    f"<a href='{self._dr_page_mapping[superseded_by_id].url_relative_to(page.file)}'>{superseded_by}</a>"
                )
            )

        meta_info = "\n".join(_meta_table(meta))

        header = title if DR_TITLE_WITH_NUM.match(title) else f"{dr_id:03d} - {title}"
        page.title = header

        return f"{header}\n===\n{meta_info}\n{markdown}"

    @property
    def lifecycles(self) -> dict[str, str]:
        configured_lifecycle_colors = self.config.get(CONFIG_LIFECYCLE_COLORS_KEY, {})
        return {
            **DEFAULT_LIFECYCLE_COLORS,
            **configured_lifecycle_colors,
        }

    @property
    def required_deciders_count(self):
        return self.config.get(
            CONFIG_REQUIRED_DECIDERS_COUNT_KEY, CONFIG_REQUIRED_DECIDERS_COUNT_DEFAULT
        )

    def _ensure_page_is_unique(self, dr_id, files, page):
        same_id_pages = [p.src_path for p in files.documentation_pages() if
                         p is not page.file and p.page and p.page.meta and p.page.meta.get("id", None) == dr_id]
        if len(same_id_pages) > 0:
            pages = ", ".join(same_id_pages)
            raise InvalidMetaDataError(page, "id", f"Uses same id as {pages}")

    def _create_status_badge(self, page):
        status = _require_meta(page, "status")

        status_color = self.lifecycles.get(status, None)
        if status_color is None:
            raise InvalidMetaDataError(page, "status", f"Invalid status {status}")

        return (
            f"<span style='color: white;background:{status_color};padding:.4em;border-radius:8px;font-size:100%;'>"
            f"{status}"
            f"</span>"
        )

    def _ticket_text(self, ticket: str):
        if self.config.get(CONFIG_TICKET_URL_PREFIX) is not None:
            return f"<a href='{self.config.get(CONFIG_TICKET_URL_PREFIX)}/{ticket}'>{ticket.upper()}</a>"

        return ticket.upper()
