"""
Reference data service.

Loads data/lookup/crops.json and data/lookup/districts.json ONCE at
application startup and keeps them in memory. This is deliberately not
a database table for v1 — the list of supported crops/districts changes
rarely, and a JSON file the project coordinator (or client) can edit
without touching code is the right amount of flexibility for now.

If the client later wants self-service crop/district management through
an admin UI, this becomes a database table with the exact same interface
(get_crop, get_district, list_crops, list_districts) — calling code
doesn't change.
"""

import json
from pathlib import Path
from typing import Optional

from app.core.exceptions import UnsupportedCropError, UnsupportedDistrictError
from app.core.logging_config import get_logger

logger = get_logger(__name__)

LOOKUP_DIR = Path("data/lookup")


class ReferenceDataService:
    def __init__(self) -> None:
        self._crops: dict[str, dict] = {}
        self._districts: dict[str, dict] = {}
        self._loaded = False

    def load(self) -> None:
        crops_path = LOOKUP_DIR / "crops.json"
        districts_path = LOOKUP_DIR / "districts.json"

        if not crops_path.exists():
            raise FileNotFoundError(
                f"Required lookup file missing: {crops_path}. "
                f"The system cannot validate crop input without it."
            )
        if not districts_path.exists():
            raise FileNotFoundError(
                f"Required lookup file missing: {districts_path}. "
                f"The system cannot validate location input without it."
            )

        # utf-8-sig transparently strips a UTF-8 byte order mark if present
        # (and is a no-op if there isn't one). Plain "utf-8" rejects a BOM
        # outright, which Windows tools commonly write when these lookup
        # files are edited/saved (e.g. PowerShell's Set-Content -Encoding utf8).
        with open(crops_path, encoding="utf-8-sig") as f:
            self._crops = json.load(f)
        with open(districts_path, encoding="utf-8-sig") as f:
            self._districts = json.load(f)

        if not self._crops:
            raise ValueError("crops.json loaded but contains no crops")
        if not self._districts:
            raise ValueError("districts.json loaded but contains no districts")

        self._loaded = True
        logger.info(
            f"Reference data loaded: {len(self._crops)} crops, "
            f"{len(self._districts)} districts"
        )

    def _ensure_loaded(self) -> None:
        if not self._loaded:
            raise RuntimeError(
                "ReferenceDataService used before load() was called. "
                "This indicates a startup-order bug."
            )

    def get_crop(self, crop: str) -> dict:
        self._ensure_loaded()
        crop = crop.strip().lower()
        if crop not in self._crops:
            raise UnsupportedCropError(
                message=(
                    f"'{crop}' is not a supported crop. "
                    f"Supported crops: {', '.join(sorted(self._crops.keys()))}"
                )
            )
        return self._crops[crop]

    def get_district(self, district: str) -> dict:
        self._ensure_loaded()
        district = district.strip().lower()
        if district not in self._districts:
            raise UnsupportedDistrictError(
                message=(
                    f"'{district}' is not a supported district. "
                    f"Supported districts: {', '.join(sorted(self._districts.keys()))}"
                )
            )
        return self._districts[district]

    def list_crops(self) -> list[str]:
        self._ensure_loaded()
        return sorted(self._crops.keys())

    def list_districts(self) -> list[str]:
        self._ensure_loaded()
        return sorted(self._districts.keys())

    def is_valid_crop(self, crop: str) -> bool:
        self._ensure_loaded()
        return crop.strip().lower() in self._crops

    def is_valid_district(self, district: str) -> bool:
        self._ensure_loaded()
        return district.strip().lower() in self._districts


# Single shared instance — loaded once during app startup (see app/main.py)
reference_data = ReferenceDataService()
