from __future__ import annotations

import json
import math
from pathlib import Path
from typing import Any

from .mission import MissionParameters, VirtualField


def _load_object(path: Path) -> dict[str, Any]:
    try:
        value = json.loads(path.read_text(encoding="utf-8"))
    except FileNotFoundError as error:
        raise FileNotFoundError(f"study configuration does not exist: {path}") from error
    if not isinstance(value, dict):
        raise ValueError(f"study configuration must be a JSON object: {path}")
    if value.get("schemaVersion") != 1:
        raise ValueError(f"unsupported study configuration schema: {path}")
    return value


def load_mission_parameters(path: str | Path) -> MissionParameters:
    target = Path(path)
    value = _load_object(target)
    if value.get("simulationOnly") is not True:
        raise ValueError("mission configuration must remain simulationOnly")
    if value.get("aircraftModel") != "DJI Mavic 3 Multispectral":
        raise ValueError("mission configuration aircraftModel must remain DJI Mavic 3 Multispectral")
    if value.get("notForFlight") is not True or value.get("flightExecutionAuthorized") is not False:
        raise ValueError("mission configuration must remain explicitly non-operational")
    if value.get("operatorFinalReviewRequired") is not True:
        raise ValueError("mission configuration must require final operator review")

    defaults = MissionParameters()
    parameters = MissionParameters(
        altitude_agl_m=float(value["altitudeAglM"]),
        requested_speed_m_s=float(value["requestedSpeedMS"]),
        front_overlap_pct=float(value["frontOverlapPct"]),
        side_overlap_pct=float(value["sideOverlapPct"]),
        capture_interval_s=float(value["multispectralCaptureIntervalS"]),
        turn_dwell_s=float(value.get("turnDwellS", defaults.turn_dwell_s)),
        takeoff_and_landing_allowance_s=float(
            value.get("takeoffAndLandingAllowanceS", defaults.takeoff_and_landing_allowance_s)
        ),
        nominal_endurance_fraction_used_for_budget=float(
            value["nominalEnduranceFractionUsedForPlanningBudget"]
        ),
        sun_altitude_deg=float(value["sunAltitudeDeg"]),
    )
    parameters.validate()
    if not 0 < parameters.nominal_endurance_fraction_used_for_budget <= 1:
        raise ValueError("nominal endurance planning fraction must be within (0, 1]")
    return parameters


def load_virtual_field(path: str | Path) -> VirtualField:
    target = Path(path)
    value = _load_object(target)
    if value.get("notForFlight") is not True:
        raise ValueError("virtual-field configuration must remain notForFlight")
    if value.get("containsRealParcel") is not False or value.get("containsPreciseCoordinates") is not False:
        raise ValueError("public digital-twin configuration cannot contain a real parcel or precise coordinates")

    field = VirtualField(
        field_id=str(value["fieldId"]),
        length_m=float(value["lengthM"]),
        width_m=float(value["widthM"]),
        crop=str(value["crop"]),
        region_context=str(value["regionContext"]),
    )
    if field.length_m <= 0 or field.width_m <= 0:
        raise ValueError("virtual-field dimensions must be positive")
    declared_area = float(value["areaHa"])
    if not math.isclose(field.area_m2 / 10_000, declared_area, rel_tol=0, abs_tol=1e-9):
        raise ValueError("declared areaHa does not match virtual-field dimensions")
    return field


def load_study_configuration(
    study_root: str | Path,
    mission_config: str | Path | None = None,
    field_config: str | Path | None = None,
) -> tuple[VirtualField, MissionParameters, dict[str, Path]]:
    root = Path(study_root)
    mission_path = Path(mission_config) if mission_config is not None else root / "config" / "mission.example.json"
    field_path = Path(field_config) if field_config is not None else root / "config" / "virtual-field.json"
    field = load_virtual_field(field_path)
    parameters = load_mission_parameters(mission_path)
    return field, parameters, {"mission": mission_path.resolve(), "field": field_path.resolve()}
