from __future__ import annotations

import json
import os
import sys
import tempfile
import time
import warnings
from functools import lru_cache
from pathlib import Path

import structlog
from platformdirs import user_config_dir

logger = structlog.get_logger()

_LEGACY_DIR = Path.home() / ".config" / "aegro"
_CRED_FILE = "credentials.json"
_STATE_FILE = "state.json"


@lru_cache(maxsize=1)
def get_config_dir() -> Path:
    """Return the native config directory, migrating legacy files if needed.

    Resolution order:
      1. AEGRO_CONFIG_DIR env var (explicit override for tests/power users)
      2. platformdirs native path per OS:
         - Linux:   ~/.config/aegro/
         - macOS:   ~/Library/Application Support/aegro/
         - Windows: C:\\Users\\<user>\\AppData\\Local\\aegro\\
    """
    override = os.environ.get("AEGRO_CONFIG_DIR")
    if override:
        return Path(override)

    native = Path(user_config_dir("aegro", appauthor=False))

    # Linux: platformdirs returns ~/.config/aegro/ — no migration needed
    if sys.platform == "linux":
        return native

    # macOS/Windows: migrate legacy files if they exist
    _migrate_legacy_file(native, _CRED_FILE)
    _migrate_legacy_file(native, _STATE_FILE)

    return native


def _migrate_legacy_file(native_dir: Path, filename: str) -> None:
    """Migrate a file from the legacy dir to the native dir via atomic write.

    - Validates JSON before promoting (won't propagate corruption)
    - Writes tempfile in the same directory (avoids cross-device OSError)
    - Renames legacy to .migrated (prevents repeated warnings)
    - Best-effort: migration failure is logged but never fatal
    """
    legacy_file = _LEGACY_DIR / filename
    native_file = native_dir / filename

    if not legacy_file.exists() or native_file.exists():
        return

    # Validate JSON before migrating (try UTF-8, fall back to CP1252)
    try:
        content = legacy_file.read_text(encoding="utf-8")
    except UnicodeDecodeError:
        try:
            content = legacy_file.read_text(encoding="cp1252")
            logger.info(
                "legacy_file_read_as_cp1252",
                path=str(legacy_file),
            )
        except (UnicodeDecodeError, OSError) as exc:
            logger.warning(
                "legacy_file_unreadable",
                path=str(legacy_file),
                error=str(exc),
            )
            return

    try:
        json.loads(content)
    except json.JSONDecodeError as exc:
        logger.warning(
            "legacy_file_corrupted",
            path=str(legacy_file),
            error=str(exc),
        )
        return

    # Atomic write: tempfile in same dir → os.replace
    # Migration is best-effort — never prevent CLI startup
    try:
        native_dir.mkdir(parents=True, exist_ok=True)
        tmp_path = None
        try:
            fd, tmp_path = tempfile.mkstemp(dir=str(native_dir), suffix=".tmp")
            with os.fdopen(fd, "w", encoding="utf-8") as f:
                f.write(content)
                f.flush()
                os.fsync(f.fileno())
            _atomic_replace(tmp_path, str(native_file))
            tmp_path = None  # replace succeeded
        finally:
            if tmp_path is not None:
                try:
                    os.unlink(tmp_path)
                except OSError:
                    pass

        # Rename legacy to prevent repeated warnings
        migrated = legacy_file.with_suffix(".json.migrated")
        renamed = False
        try:
            legacy_file.rename(migrated)
            renamed = True
        except OSError:
            pass  # non-critical if rename fails

        if native_file.exists():
            suffix = (
                f" Original renamed to {migrated.name}." if renamed else " Original left in place."
            )
            warnings.warn(
                f"Config migrated from {legacy_file} to {native_file}.{suffix}",
                UserWarning,
                stacklevel=3,
            )
    except Exception as exc:
        logger.warning(
            "legacy_migration_failed",
            path=str(legacy_file),
            native_dir=str(native_dir),
            error=str(exc),
        )


def _atomic_replace(src: str, dst: str) -> None:
    """os.replace with increasing backoff for Windows file locking.

    Backoff delays: 0.05s, 0.1s, 0.25s, 0.5s, 1.0s (~2s total).
    Only retries PermissionError on Windows; re-raises immediately elsewhere.
    """
    delays = [0.05, 0.1, 0.25, 0.5, 1.0]
    for attempt in range(len(delays) + 1):
        try:
            os.replace(src, dst)
            return
        except PermissionError:
            if attempt < len(delays) and sys.platform == "win32":
                logger.debug(
                    "atomic_replace_retry",
                    attempt=attempt + 1,
                    src=src,
                    dst=dst,
                )
                time.sleep(delays[attempt])
            else:
                raise
