"""Website making/updating module: initialize, build, and deploy static sites."""

import json
import logging
import re
import shutil
import threading
from http.server import HTTPServer, SimpleHTTPRequestHandler
from pathlib import Path
from typing import Optional

logger = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# Constants / templates
# ---------------------------------------------------------------------------

_BASE_CSS = """\
:root {
  --primary: #1a365d;
  --accent: #2b6cb0;
  --bg: #ffffff;
  --text: #1a202c;
  --light-bg: #f7fafc;
  --border: #e2e8f0;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', system-ui, sans-serif; color: var(--text);
       line-height: 1.6; max-width: 900px; margin: 0 auto; padding: 2rem; }
nav { display: flex; gap: 1.5rem; padding: 1rem 0; border-bottom: 1px solid var(--border); margin-bottom: 2rem; }
nav a { text-decoration: none; color: var(--accent); font-weight: 500; }
nav a:hover { text-decoration: underline; }
h1, h2, h3 { color: var(--primary); margin-bottom: 0.5rem; }
section { margin-bottom: 2rem; }
.pub-entry { margin-bottom: 1rem; padding: 0.75rem; background: var(--light-bg);
             border-left: 3px solid var(--accent); }
footer { margin-top: 3rem; padding-top: 1rem; border-top: 1px solid var(--border);
         font-size: 0.85rem; color: #718096; }
"""

SUPPORTED_DEPLOY_METHODS = {"github-pages", "netlify", "manual"}

SUPPORTED_TEMPLATES = {"academic", "minimal"}

CV_SECTIONS = ("education", "experience", "skills", "awards", "service")


def _nav_html(pages: list[tuple[str, str]]) -> str:
    """Return an HTML nav bar from a list of (filename, label) pairs."""
    links = "".join(f'  <a href="{fn}">{label}</a>\n' for fn, label in pages)
    return f"<nav>\n{links}</nav>\n"


def _wrap_page(title: str, body: str, nav: str) -> str:
    """Wrap body content in a full HTML page."""
    return (
        '<!DOCTYPE html>\n<html lang="en">\n<head>\n'
        '  <meta charset="UTF-8">\n'
        '  <meta name="viewport" content="width=device-width, initial-scale=1.0">\n'
        f"  <title>{title}</title>\n"
        '  <link rel="stylesheet" href="css/style.css">\n'
        "</head>\n<body>\n"
        f"{nav}\n"
        f"{body}\n"
        "<footer>Generated by ricet</footer>\n"
        "</body>\n</html>\n"
    )


# ---------------------------------------------------------------------------
# WebsiteProject class
# ---------------------------------------------------------------------------


class WebsiteProject:
    """Manage a website project directory."""

    def __init__(self, path: Path) -> None:
        self.path = Path(path)
        if not self.path.exists():
            raise FileNotFoundError(f"Project directory does not exist: {self.path}")
        self._config_path = self.path / "site.json"

    # -- properties ----------------------------------------------------------

    @property
    def config(self) -> dict:
        """Return the site configuration dict."""
        if self._config_path.exists():
            return json.loads(self._config_path.read_text())
        return {}

    @property
    def pages(self) -> list[str]:
        """Return a sorted list of HTML page filenames in the project root."""
        return sorted(p.name for p in self.path.glob("*.html"))

    # -- helpers -------------------------------------------------------------

    def save_config(self, cfg: dict) -> None:
        self._config_path.write_text(json.dumps(cfg, indent=2) + "\n")


# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------


def init_website(path: Path, template: str = "academic") -> Path:
    """Initialize a website project at *path* using the given template.

    If the directory already exists, missing scaffolding files are added but
    existing files are **not** overwritten (idempotent).

    Returns the project root path.
    """
    path = Path(path)
    path.mkdir(parents=True, exist_ok=True)
    css_dir = path / "css"
    css_dir.mkdir(exist_ok=True)

    config_path = path / "site.json"
    if not config_path.exists():
        config = {"template": template, "title": path.name, "pages": []}
        config_path.write_text(json.dumps(config, indent=2) + "\n")

    # CSS
    css_file = css_dir / "style.css"
    if not css_file.exists():
        css_file.write_text(_BASE_CSS)

    if template == "academic":
        nav_items = [
            ("index.html", "Home"),
            ("publications.html", "Publications"),
            ("cv.html", "CV"),
        ]
    else:
        nav_items = [("index.html", "Home")]

    nav = _nav_html(nav_items)

    # index.html
    _write_if_missing(
        path / "index.html",
        _wrap_page("Home", "<h1>Welcome</h1>\n<p>This is my website.</p>", nav),
    )

    if template == "academic":
        _write_if_missing(
            path / "publications.html",
            _wrap_page(
                "Publications",
                '<h1>Publications</h1>\n<div id="pub-list">\n</div>',
                nav,
            ),
        )
        cv_body_parts = [
            f'<section id="cv-{s}">\n<h2>{s.title()}</h2>\n</section>'
            for s in CV_SECTIONS
        ]
        _write_if_missing(
            path / "cv.html",
            _wrap_page(
                "CV", "<h1>Curriculum Vitae</h1>\n" + "\n".join(cv_body_parts), nav
            ),
        )

    logger.info("Initialized %s website at %s", template, path)
    return path


def update_page(page: str, content: str, project_path: Path) -> bool:
    """Replace the content of an existing page.

    The page's ``<body>`` inner content (between nav and footer) is replaced.
    Returns True on success, False if the page does not exist.
    """
    target = Path(project_path) / page
    if not target.exists():
        return False
    # Read existing page to preserve nav
    existing = target.read_text()
    # Replace body content between nav closing and footer
    new_html = _replace_body_content(existing, content)
    target.write_text(new_html)
    logger.info("Updated page %s", page)
    return True


def add_page(title: str, content: str, project_path: Path) -> Path:
    """Create a new page and return its path.

    The filename is derived from the title (lowercased, spaces to hyphens).
    """
    project_path = Path(project_path)
    slug = re.sub(r"[^a-z0-9]+", "-", title.lower()).strip("-")
    filename = f"{slug}.html"
    filepath = project_path / filename

    # Build nav from existing pages + new page
    wp = WebsiteProject(project_path)
    nav_items = [
        (p, p.replace(".html", "").replace("-", " ").title()) for p in wp.pages
    ]
    nav_items.append((filename, title))
    nav = _nav_html(nav_items)

    body = f"<h1>{title}</h1>\n{content}"
    filepath.write_text(_wrap_page(title, body, nav))

    # Update config
    cfg = wp.config
    if "pages" not in cfg:
        cfg["pages"] = []
    if filename not in cfg["pages"]:
        cfg["pages"].append(filename)
    wp.save_config(cfg)

    logger.info("Added page %s", filename)
    return filepath


def build_site(project_path: Path) -> bool:
    """Build the site by copying source files into a ``_build`` directory.

    Returns True on success.
    """
    project_path = Path(project_path)
    build_dir = project_path / "_build"
    if build_dir.exists():
        shutil.rmtree(build_dir)
    build_dir.mkdir()

    for item in project_path.iterdir():
        if item.name.startswith("_") or item.name.startswith("."):
            continue
        dest = build_dir / item.name
        if item.is_dir():
            shutil.copytree(item, dest)
        else:
            shutil.copy2(item, dest)

    logger.info("Built site into %s", build_dir)
    return True


def deploy_site(project_path: Path, method: str = "github-pages") -> dict:
    """Deploy the built site using the specified method.

    Returns a dict with deployment info including ``status`` and ``method``.
    """
    project_path = Path(project_path)
    build_dir = project_path / "_build"

    if method not in SUPPORTED_DEPLOY_METHODS:
        return {
            "status": "error",
            "method": method,
            "message": f"Unsupported deploy method: {method}",
        }

    if not build_dir.exists():
        # Auto-build if needed
        build_site(project_path)

    if method == "github-pages":
        # Create a .nojekyll file so GitHub Pages serves raw HTML
        (build_dir / ".nojekyll").touch()
        return {
            "status": "ready",
            "method": "github-pages",
            "build_dir": str(build_dir),
            "message": "Push the _build directory to the gh-pages branch.",
        }
    elif method == "netlify":
        return {
            "status": "ready",
            "method": "netlify",
            "build_dir": str(build_dir),
            "message": "Set publish directory to _build in Netlify.",
        }
    else:  # manual
        return {
            "status": "ready",
            "method": "manual",
            "build_dir": str(build_dir),
            "message": "Upload the _build directory to your server.",
        }


def add_publication(bib_entry: str, project_path: Path) -> bool:
    """Parse a BibTeX entry and append it to the publications page.

    Returns True on success, False if the publications page does not exist.
    """
    project_path = Path(project_path)
    pub_page = project_path / "publications.html"
    if not pub_page.exists():
        return False

    # Simple BibTeX field extraction
    fields = _parse_bib_fields(bib_entry)
    title = fields.get("title", "Untitled")
    author = fields.get("author", "")
    year = fields.get("year", "")
    journal = fields.get("journal", "")

    entry_html = (
        '<div class="pub-entry">\n'
        f"  <strong>{title}</strong><br>\n"
        f"  {author}<br>\n"
        f"  <em>{journal}</em>, {year}\n"
        "</div>\n"
    )

    content = pub_page.read_text()
    # Insert before the closing </div> of pub-list
    marker = "</div>"
    idx = content.find('id="pub-list"')
    if idx != -1:
        # Find the closing </div> for pub-list
        close_idx = content.find(marker, idx)
        if close_idx != -1:
            content = content[:close_idx] + entry_html + content[close_idx:]
    else:
        # Fallback: insert before </body>
        content = content.replace("</body>", entry_html + "</body>")

    pub_page.write_text(content)
    logger.info("Added publication: %s", title)
    return True


def update_cv(section: str, content: str, project_path: Path) -> bool:
    """Update a section of the CV page.

    The *section* name (e.g. ``'education'``) identifies the ``<section>``
    element by its ``id`` attribute (``cv-education``).

    Returns True on success, False if the CV page does not exist.
    """
    project_path = Path(project_path)
    cv_page = project_path / "cv.html"
    if not cv_page.exists():
        return False

    section_id = f"cv-{section}"
    html = cv_page.read_text()

    # Find the section and replace its inner content
    pattern = rf'(<section id="{section_id}">)\s*<h2>[^<]*</h2>\s*(.*?)(</section>)'
    replacement = rf"\1\n<h2>{section.title()}</h2>\n{content}\n\3"
    new_html, count = re.subn(pattern, replacement, html, flags=re.DOTALL)

    if count == 0:
        # Section not found; append a new one before </body>
        new_section = (
            f'<section id="{section_id}">\n'
            f"<h2>{section.title()}</h2>\n"
            f"{content}\n"
            "</section>\n"
        )
        new_html = html.replace("</body>", new_section + "</body>")

    cv_page.write_text(new_html)
    logger.info("Updated CV section: %s", section)
    return True


def preview_site(project_path: Path, port: int = 8000) -> str:
    """Start a local HTTP preview server for the built site.

    Returns the URL string (e.g. ``http://localhost:8000``).
    The server runs in a daemon thread so it stops when the process exits.
    """
    project_path = Path(project_path)
    build_dir = project_path / "_build"
    if not build_dir.exists():
        build_site(project_path)

    handler = _make_handler(build_dir)
    server = HTTPServer(("", port), handler)
    thread = threading.Thread(target=server.serve_forever, daemon=True)
    thread.start()

    url = f"http://localhost:{port}"
    logger.info("Preview server running at %s", url)
    return url


# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------


def _write_if_missing(path: Path, content: str) -> None:
    """Write *content* to *path* only if the file does not already exist."""
    if not path.exists():
        path.write_text(content)


def _replace_body_content(html: str, new_content: str) -> str:
    """Replace the main body content of an HTML page, preserving nav/footer."""
    # Strategy: replace everything between </nav> and <footer>
    pattern = r"(</nav>\s*)(.*?)(\s*<footer>)"
    replacement = rf"\1\n{new_content}\n\3"
    result, n = re.subn(pattern, replacement, html, count=1, flags=re.DOTALL)
    if n == 0:
        # Fallback: replace between <body> and </body>
        pattern = r"(<body>\s*)(.*?)(\s*</body>)"
        replacement = rf"\1\n{new_content}\n\3"
        result = re.sub(pattern, replacement, html, count=1, flags=re.DOTALL)
    return result


def _parse_bib_fields(bib: str) -> dict[str, str]:
    """Extract key=value fields from a BibTeX entry string."""
    fields: dict[str, str] = {}
    for match in re.finditer(r"(\w+)\s*=\s*\{([^}]*)\}", bib):
        fields[match.group(1).lower()] = match.group(2).strip()
    return fields


def _make_handler(directory: Path):
    """Create a SimpleHTTPRequestHandler bound to the given directory."""

    class Handler(SimpleHTTPRequestHandler):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, directory=str(directory), **kwargs)

        def log_message(self, format, *args):  # noqa: A002
            logger.debug(format, *args)

    return Handler


# ---------------------------------------------------------------------------
# CLI adapter — ``from core.website import site_manager``
# ---------------------------------------------------------------------------


class _SiteManager:
    """Thin CLI-facing adapter wrapping the module-level website functions."""

    def __init__(self, project_path: Optional[Path] = None):
        self._path = project_path or Path(".")

    def init(self, template: str = "academic") -> None:
        init_website(self._path, template=template)

    def build(self) -> bool:
        return build_site(self._path)

    def deploy(self, method: str = "github-pages") -> dict:
        return deploy_site(self._path, method=method)

    def preview(self, port: int = 8000) -> str:
        return preview_site(self._path, port=port)


site_manager = _SiteManager()
