"""Frontend registration for TaskMate custom cards."""

from __future__ import annotations

import json
import logging
from pathlib import Path
from typing import Final

from homeassistant.components.frontend import add_extra_js_url
from homeassistant.components.http import StaticPathConfig
from homeassistant.core import HomeAssistant

from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)

# URL base path for serving static files
URL_BASE: Final = "/taskmate"

# Lovelace resources. Listed in load order: shared utilities first so that
# window.__taskmate_attrs / __taskmate_localize are reliably defined by the
# time card modules execute. Without this, a cold-cache load could render
# cards before the utility globals existed, producing empty chore lists and
# raw localisation keys for non-admin users.
CARDS: Final = [
    "taskmate-attr-resolver.js",
    "taskmate-localize.js",
    "taskmate-design.js",
    "taskmate-badges-card.js",
    "taskmate-child-card.js",
    "taskmate-routine-card.js",
    "taskmate-rewards-card.js",
    "taskmate-approvals-card.js",
    "taskmate-points-card.js",
    "taskmate-reorder-card.js",
    "taskmate-overview-card.js",
    "taskmate-activity-card.js",
    "taskmate-streak-card.js",
    "taskmate-weekly-card.js",
    "taskmate-graph-card.js",
    "taskmate-reward-progress-card.js",
    "taskmate-leaderboard-card.js",
    "taskmate-parent-dashboard-card.js",
    "taskmate-penalties-card.js",
    "taskmate-bonuses-card.js",
    "taskmate-points-display-card.js",
    "taskmate-calendar-card.js",
    "taskmate-photo-gallery-card.js",
    "taskmate-family-goal-card.js",
]

# Cards that USED to ship but were removed. Their files no longer exist, so any
# Lovelace resource still registered for them 404s on every dashboard load for
# users upgrading from a version that had them. We deregister these by EXACT
# URL only (never a heuristic diff — that once wiped every resource, which is
# why blanket stale-cleanup was removed from async_register_cards).
RETIRED_CARDS: Final = [
    "taskmate-task-groups-card.js",  # removed #452
    "taskmate-templates-card.js",  # removed #448
    "taskmate-reminders-card.js",  # removed #450
]

# JS modules loaded on every HA frontend page (config flow sound preview).
# taskmate-localize.js is ALSO listed in CARDS (Lovelace resources) so it loads
# early on dashboards; it must be here too because the admin panel (panel.py) is
# a panel_custom page, not a Lovelace dashboard, and never loads Lovelace
# resources — without this the panel renders raw i18n keys. Double-loading is a
# no-op: the module is keyed by URL (loaded once) and only assigns idempotent
# window.__taskmate_localize globals.
GLOBAL_MODULES: Final = [
    "taskmate-config-sounds.js",
    "taskmate-localize.js",
    # Like localize.js above, the admin panel (a panel_custom page, not a
    # Lovelace dashboard) never loads Lovelace resources, so the shared design
    # layer must be loaded globally for window.__taskmate_design to exist when
    # the panel resolves/stamps its design. Double-loading is a no-op (keyed by
    # URL, assigns idempotent globals).
    "taskmate-design.js",
]

# Track if frontend is registered
FRONTEND_REGISTERED: Final = "frontend_registered"


async def _async_get_version(hass: HomeAssistant) -> str:
    """Get version from manifest.json for cache busting (async-safe)."""
    manifest_path = Path(__file__).parent / "manifest.json"
    try:
        content = await hass.async_add_executor_job(manifest_path.read_text, "utf-8")
        return json.loads(content).get("version", "1.0.0")
    except (OSError, json.JSONDecodeError, AttributeError):
        return "1.0.0"


async def async_register_frontend(hass: HomeAssistant) -> None:
    """Register static paths for serving card JavaScript files."""
    # Only register once
    if hass.data.get(DOMAIN, {}).get(FRONTEND_REGISTERED):
        _LOGGER.debug("Frontend already registered, skipping")
        return

    www_path = Path(__file__).parent / "www"

    if not www_path.exists():
        _LOGGER.warning("www directory not found at %s", www_path)
        return

    # Register the www folder as a static path.
    #
    # This call is what sets our minimum Home Assistant version. Both
    # async_register_static_paths and StaticPathConfig landed in 2024.7.0 and do
    # not exist in 2024.6.0, so on anything older setup dies here with an
    # AttributeError rather than degrading — hence the 2024.7.0 floor in
    # hacs.json and README.md. Lowering that floor means adding a fallback to
    # the old (since-removed) hass.http.register_static_path.
    await hass.http.async_register_static_paths([StaticPathConfig(URL_BASE, str(www_path), False)])

    _LOGGER.debug("Registered static path: %s -> %s", URL_BASE, www_path)

    # Authenticated upload/serve endpoints for chore evidence photos.
    from .http_photos import async_register_photo_views

    async_register_photo_views(hass)

    # Admin-gated upload / authenticated serve for chore pictures (#750).
    from .http_images import async_register_image_views

    async_register_image_views(hass)

    # Token-gated ICS calendar feed (FEAT-10).
    from .http_calendar import async_register_calendar_view

    async_register_calendar_view(hass)

    # Register global JS modules (loaded on all pages, including config flow)
    version = await _async_get_version(hass)
    for module in GLOBAL_MODULES:
        module_url = f"{URL_BASE}/{module}?v={version}"
        add_extra_js_url(hass, module_url)
        _LOGGER.info("Registered global frontend module: %s", module_url)

    # Mark as registered
    hass.data.setdefault(DOMAIN, {})[FRONTEND_REGISTERED] = True


async def async_register_cards(hass: HomeAssistant) -> None:
    """Register and version-update TaskMate Lovelace resources on every startup.

    Safety rules — this function will ONLY ever:
      1. Add missing TaskMate cards (create)
      2. Update the ?v= query string on existing TaskMate cards (update)
    It will NEVER delete any resource. Stale cleanup is removed entirely
    because URL mismatches caused accidental deletion of all resources.
    Only URLs that begin with /taskmate/ are ever touched.
    """
    version = await _async_get_version(hass)
    _LOGGER.info("TaskMate resource manager: version=%s", version)

    lovelace_data = hass.data.get("lovelace")
    if lovelace_data is None:
        _LOGGER.warning("TaskMate: Lovelace not available — skipping resource registration.")
        return

    mode = getattr(lovelace_data, "mode", "storage")
    if mode == "yaml":
        _LOGGER.info("TaskMate: Lovelace YAML mode — add resources manually:")
        for card in CARDS:
            _LOGGER.info("  - url: %s/%s?v=%s  (type: module)", URL_BASE, card, version)
        return

    try:
        resources = lovelace_data.resources
        if resources is None:
            _LOGGER.warning("TaskMate: Lovelace resources object not available.")
            return

        # Force load storage from disk BEFORE reading items.
        # Without this, async_items() returns empty if storage hasn't been
        # read yet — causing us to create duplicate entries which then get
        # wiped when lovelace subsequently loads its own storage file.
        #
        # Browser Mod uses: resources.async_load() + resources.loaded flag
        # WebRTC uses:      resources.async_get_info()
        # We use both as a belt-and-braces approach.
        if hasattr(resources, "async_load"):
            await resources.async_load()
        if hasattr(resources, "async_get_info"):
            await resources.async_get_info()

        # Build a map of base_url (without ?v=...) -> full resource item
        # ONLY for resources whose URL starts with /taskmate/
        # Everything else is completely ignored
        existing: dict[str, dict] = {}
        all_items = list(resources.async_items())
        _LOGGER.debug("TaskMate: total Lovelace resources = %d", len(all_items))

        for item in all_items:
            url = item.get("url", "")
            base_url = url.split("?")[0]
            if base_url.startswith(URL_BASE + "/"):
                existing[base_url] = item
                _LOGGER.debug("TaskMate: found existing resource: %s", url)

        _LOGGER.info("TaskMate: found %d existing TaskMate resources", len(existing))

        # Add missing cards or update version on existing ones
        # NEVER delete anything
        for card in CARDS:
            card_url = f"{URL_BASE}/{card}"
            versioned_url = f"{card_url}?v={version}"

            if card_url not in existing:
                # Card not registered yet — add it
                await resources.async_create_item({"url": versioned_url, "res_type": "module"})
                _LOGGER.info("TaskMate: added resource: %s", versioned_url)
            else:
                item = existing[card_url]
                current_url = item.get("url", "")
                if current_url != versioned_url:
                    # Version string changed — update it
                    await resources.async_update_item(
                        item["id"],
                        {"url": versioned_url, "res_type": "module"},
                    )
                    _LOGGER.info(
                        "TaskMate: updated resource: %s -> %s",
                        current_url,
                        versioned_url,
                    )
                else:
                    _LOGGER.debug("TaskMate: resource up to date: %s", versioned_url)

        # Deregister retired cards by EXACT URL match only. This is the one
        # delete we allow: each target is a specific /taskmate/<file>.js that no
        # longer exists, looked up in the existing map we already built. No
        # diffing of "unexpected" URLs, so it cannot cascade into deleting live
        # resources the way the old blanket cleanup did.
        if hasattr(resources, "async_delete_item"):
            for retired in RETIRED_CARDS:
                item = existing.get(f"{URL_BASE}/{retired}")
                if item is None:
                    continue
                try:
                    await resources.async_delete_item(item["id"])
                    _LOGGER.info("TaskMate: removed retired resource: %s", item.get("url"))
                except (AttributeError, KeyError, TypeError, OSError) as err:
                    _LOGGER.warning(
                        "TaskMate: could not remove retired resource %s: %s",
                        item.get("url"),
                        err,
                    )

    except (AttributeError, KeyError, TypeError, OSError) as err:
        _LOGGER.error("TaskMate: error managing Lovelace resources: %s", err)
