import os
import re
from pathlib import Path

import yaml
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.nav import Navigation
from mkdocs.structure.pages import Page
from mkdocs.utils.templates import TemplateContext

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,}.*")

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),
        ),
    )


    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.",
                )


            meta.append(
                (
                    "Superseded by",
                    f"{superseded_by:03d}"
                )
            )

        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()
