# Production log landing on pog-atlas.com:51709 via curl -T (not rclone, not FTP wput).
#
# Fixture Dropbox capture (install_sync.sh) uses hostname / FIXTURE_POS → logs/N.
# This resolver does not. Dest is Enrollment Path /Logs/, or /orphan/{asset}/Logs/
# when the asset is set but not enrolled. Asset unset → skip IIS.
#
# Enable/disable is Identity /config (config.ini log_upload_enabled), not rclone.env.
# 51709 WebDAV PUT uses IIS Basic auth. Pi stores user/password in wput.env (not git).
#
# Archive names: {asset}-{UTC}-{product}-{live-basename}.gz
# Lucky Street 50003 → .../St_Thomas/D-Tutu/Lucky_Street_E-C/Logs/

from __future__ import annotations

import argparse
import configparser
import json
import os
import sys
from typing import Dict, Optional

from enrollment import (
    ASSET_UNSET,
    _config_dir,
    _normalize_path,
    lookup_enrollment,
    resolve_enrollment_csv_path,
)

DEFAULT_SUBDIR = "Logs"
DEFAULT_DOWNLOAD_IP = "pog-atlas.com"
DEFAULT_DOWNLOAD_PORT = 51709
ORPHAN_PREFIX = "/orphan/"


def archive_basename(asset: int, stamp: str, product: str, live_basename: str) -> str:
    """Name of a closed log archive (before optional .gz)."""
    base = os.path.basename(live_basename or "log")
    prod = (product or "log").strip() or "log"
    return f"{int(asset)}-{stamp}-{prod}-{base}"


def enrollment_rel_path(enrollment_path: str, subdir: str = DEFAULT_SUBDIR) -> str:
    rel = _normalize_path(enrollment_path).strip("/")
    leaf = (subdir or "").strip().strip("/")
    parts = [p for p in (rel, leaf) if p]
    if not parts:
        return ""
    return "/".join(parts) + "/"


def log_site_base(
    download_ip: str = "",
    download_port: int = DEFAULT_DOWNLOAD_PORT,
    content_base_url: str = "",
) -> str:
    explicit = (content_base_url or "").strip()
    if explicit:
        text = explicit if "://" in explicit else "https://" + explicit
        return text.rstrip("/")
    host = (download_ip or DEFAULT_DOWNLOAD_IP).strip() or DEFAULT_DOWNLOAD_IP
    port = int(download_port or DEFAULT_DOWNLOAD_PORT)
    return f"https://{host}:{port}"


def https_log_dest(content_sync_url: str, subdir: str = DEFAULT_SUBDIR) -> str:
    url = (content_sync_url or "").strip()
    if not url:
        return ""
    if not url.endswith("/"):
        url += "/"
    leaf = (subdir or "").strip().strip("/")
    if leaf:
        url += leaf + "/"
    return url


def https_log_dest_from_path(
    enrollment_path: str,
    *,
    download_ip: str = DEFAULT_DOWNLOAD_IP,
    download_port: int = DEFAULT_DOWNLOAD_PORT,
    content_base_url: str = "",
    subdir: str = DEFAULT_SUBDIR,
) -> str:
    base = log_site_base(download_ip, download_port, content_base_url)
    path = _normalize_path(enrollment_path)
    if not base or not path:
        return ""
    return https_log_dest(f"{base}{path}", subdir)


def orphan_enrollment_path(asset: int) -> str:
    return _normalize_path(f"{ORPHAN_PREFIX}{int(asset)}/")


def _load_ini(path: str):
    try:
        from extended_configparser.parser import ExtendedConfigParser

        cfg = ExtendedConfigParser()
        cfg.read(path)
        return cfg
    except Exception:
        cfg = configparser.ConfigParser(interpolation=None)
        cfg.optionxform = str
        cfg.read(path, encoding="utf-8")
        return cfg


def _config_candidates(explicit: str = "") -> list:
    out = []
    if explicit.strip():
        out.append(os.path.normpath(explicit.strip()))
    env_cfg = os.environ.get("DYZINEFRAME_CONFIG", "").strip()
    if env_cfg:
        out.append(os.path.normpath(env_cfg))
    home = os.environ.get("HOME", "").strip()
    if home:
        out.append(os.path.join(home, "DyzineFrame", "config.ini"))
    out.append(os.path.join(_config_dir(), "config.ini"))
    seen = set()
    unique = []
    for path in out:
        if path in seen:
            continue
        seen.add(path)
        unique.append(path)
    return unique


def find_config_ini(explicit: str = "") -> Optional[str]:
    for path in _config_candidates(explicit):
        if os.path.isfile(path):
            return path
    return None


def _get_opt(cfg, section: str, *names: str, default: str = "") -> str:
    """Case-insensitive INI get (ExtendedConfigParser vs stdlib)."""
    sections = {s.lower(): s for s in cfg.sections()}
    sec = sections.get(section.lower())
    if not sec:
        return default
    opts = {k.lower(): k for k in cfg.options(sec)}
    for name in names:
        key = opts.get(name.lower())
        if key is not None:
            return str(cfg.get(sec, key)).strip()
    return default


def _parse_bool(raw: str, default: bool = True) -> bool:
    text = (raw or "").strip().lower()
    if not text:
        return default
    if text in ("0", "false", "no", "off"):
        return False
    if text in ("1", "true", "yes", "on"):
        return True
    return default


def read_topbox_log_settings(cfg) -> Dict[str, object]:
    raw_asset = _get_opt(
        cfg, "TOPBOX", "ASSET_NUMBER", "asset_number", "POG_ASSET_NUMBER", "pog_asset_number"
    )
    asset = ASSET_UNSET
    if raw_asset:
        try:
            asset = int(raw_asset)
        except ValueError:
            asset = ASSET_UNSET
    raw_port = _get_opt(
        cfg, "TOPBOX", "DOWNLOAD_URL_PORT", "download_url_port",
        default=str(DEFAULT_DOWNLOAD_PORT),
    )
    try:
        port = int(raw_port or DEFAULT_DOWNLOAD_PORT)
    except ValueError:
        port = DEFAULT_DOWNLOAD_PORT
    return {
        "asset_number": asset,
        "enrollment_csv": _get_opt(cfg, "TOPBOX", "ENROLLMENT_CSV", "enrollment_csv"),
        "content_base_url": _get_opt(cfg, "TOPBOX", "CONTENT_BASE_URL", "content_base_url"),
        "download_ip": _get_opt(
            cfg, "TOPBOX", "DOWNLOAD_URL_IP", "download_url_ip", default=DEFAULT_DOWNLOAD_IP
        )
        or DEFAULT_DOWNLOAD_IP,
        "download_port": port,
        "log_upload_enabled": _parse_bool(
            _get_opt(cfg, "TOPBOX", "LOG_UPLOAD_ENABLED", "log_upload_enabled", default="True")
        ),
        "enrollment_path": _normalize_path(
            _get_opt(cfg, "TOPBOX", "ENROLLMENT_PATH", "enrollment_path")
        ),
        "enrollment_location": _get_opt(
            cfg, "TOPBOX", "ENROLLMENT_LOCATION", "enrollment_location"
        ),
    }


def resolve_production_log_dest(
    *,
    asset: Optional[int] = None,
    config_path: str = "",
    enrollment_csv: str = "",
    content_base_url: str = "",
    download_ip: str = "",
    download_port: Optional[int] = None,
    subdir: str = DEFAULT_SUBDIR,
    product: str = "",
    log_upload_enabled: Optional[bool] = None,
    enrollment_path: str = "",
) -> Dict[str, Optional[str]]:
    result: Dict[str, Optional[str]] = {
        "mode": "skip",
        "asset": None,
        "enrollment_path": None,
        "https_dest": None,
        "file_prefix": None,
        "product": (product or "").strip() or None,
        "subdir": (subdir or "").strip() or None,
        "config_ini": None,
        "kind": None,
        "error": None,
    }

    cfg_path = find_config_ini(config_path)
    settings: Dict[str, object] = {
        "asset_number": ASSET_UNSET,
        "enrollment_csv": enrollment_csv,
        "content_base_url": content_base_url,
        "download_ip": download_ip or DEFAULT_DOWNLOAD_IP,
        "download_port": int(download_port or DEFAULT_DOWNLOAD_PORT),
        "log_upload_enabled": True,
        "enrollment_path": _normalize_path(enrollment_path),
        "enrollment_location": "",
    }
    if cfg_path:
        result["config_ini"] = cfg_path
        try:
            settings.update(read_topbox_log_settings(_load_ini(cfg_path)))
        except Exception as exc:
            if asset is None:
                result["error"] = f"config.ini read failed: {exc}"
                return result

    if enrollment_csv.strip():
        settings["enrollment_csv"] = enrollment_csv.strip()
    if content_base_url.strip():
        settings["content_base_url"] = content_base_url.strip()
    if download_ip.strip():
        settings["download_ip"] = download_ip.strip()
    if download_port is not None:
        settings["download_port"] = int(download_port)
    if enrollment_path.strip():
        settings["enrollment_path"] = _normalize_path(enrollment_path)
    if log_upload_enabled is not None:
        settings["log_upload_enabled"] = bool(log_upload_enabled)

    if not bool(settings.get("log_upload_enabled", True)):
        result["error"] = "log upload disabled on Identity /config"
        return result

    if asset is None:
        use_asset = int(settings.get("asset_number") or ASSET_UNSET)
    else:
        use_asset = int(asset)
    if use_asset == ASSET_UNSET:
        result["error"] = "asset_number is unset (0) — production log upload skipped"
        return result
    result["asset"] = str(use_asset)
    result["file_prefix"] = str(use_asset)

    csv_path = resolve_enrollment_csv_path(str(settings.get("enrollment_csv") or ""))
    row = lookup_enrollment(use_asset, csv_path) if csv_path else None
    path = (row.path if row else "") or str(settings.get("enrollment_path") or "")
    kind = "enrollment"
    if not path:
        path = orphan_enrollment_path(use_asset)
        kind = "orphan"
    elif row is None:
        kind = "identity_path"

    ip = str(settings.get("download_ip") or DEFAULT_DOWNLOAD_IP)
    port = int(settings.get("download_port") or DEFAULT_DOWNLOAD_PORT)
    base_override = str(settings.get("content_base_url") or "")
    https = https_log_dest_from_path(
        path,
        download_ip=ip,
        download_port=port,
        content_base_url=base_override,
        subdir=subdir,
    )
    result["enrollment_path"] = path
    result["https_dest"] = https
    result["kind"] = kind
    result["mode"] = "production"
    result["error"] = None
    if kind == "orphan":
        result["error"] = f"asset {use_asset} not in Enrollment.csv — IIS dest /orphan/{use_asset}/Logs/"
    return result


def _shell_quote(value: str) -> str:
    return "'" + str(value).replace("'", "'\\''") + "'"


def _export_env(result: Dict[str, Optional[str]]) -> None:
    mapping = {
        "MODE": result.get("mode") or "skip",
        "ASSET": result.get("asset") or "",
        "ENROLLMENT_PATH": result.get("enrollment_path") or "",
        "HTTPS_DEST": result.get("https_dest") or "",
        "FILE_PREFIX": result.get("file_prefix") or "",
        "LOG_DEST_KIND": result.get("kind") or "",
        "LOG_DEST_ERROR": result.get("error") or "",
    }
    for key, value in mapping.items():
        print(f"{key}={_shell_quote(value)}")


def _main() -> None:
    parser = argparse.ArgumentParser(
        description="Resolve production log dest from Enrollment.csv (wput URL, not rclone)"
    )
    parser.add_argument("--asset", type=int, default=None)
    parser.add_argument("--config", default="")
    parser.add_argument("--csv", default="")
    parser.add_argument("--base-url", default="")
    parser.add_argument("--ip", default="")
    parser.add_argument("--port", type=int, default=None)
    parser.add_argument("--subdir", default=DEFAULT_SUBDIR)
    parser.add_argument("--product", default="")
    parser.add_argument("--path", default="", help="Identity enrollment_path override")
    parser.add_argument("--export-env", action="store_true")
    parser.add_argument("--json", action="store_true")
    args = parser.parse_args()

    subdir = args.subdir
    if "LOG_UPLOAD_SUBDIR" in os.environ and args.subdir == DEFAULT_SUBDIR:
        subdir = os.environ.get("LOG_UPLOAD_SUBDIR", DEFAULT_SUBDIR)

    out = resolve_production_log_dest(
        asset=args.asset,
        config_path=args.config,
        enrollment_csv=args.csv,
        content_base_url=args.base_url,
        download_ip=args.ip,
        download_port=args.port,
        subdir=subdir,
        product=args.product,
        enrollment_path=args.path,
    )
    if args.export_env:
        _export_env(out)
        return
    print(json.dumps(out, indent=2))
    if out.get("mode") == "skip":
        sys.exit(0)


if __name__ == "__main__":
    _main()
