from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import Iterable, Mapping, Any, Literal

from eth_account import Account

SUPPORTED_ENVS = {"mainnet": "Mainnet.env", "testnet": "Testnet.env"}
DEFAULT_SEARCH_DIRS = (Path("/agent/.hermes"), Path("/home/agent/.hermes"))
ValidationMode = Literal["readonly", "signed"]


def mask_address(value: str | None) -> str | None:
    if not value:
        return None
    return value[:6] + "..." + value[-4:] if value.startswith("0x") and len(value) > 12 else "<masked>"


def _normalize_env(env: str) -> str:
    env = env.lower().strip()
    if env not in SUPPORTED_ENVS:
        raise ValueError(f"unsupported hyperliquid env: {env}")
    return env


def find_env_file(env: str, *, search_dirs: Iterable[str | Path] = DEFAULT_SEARCH_DIRS) -> Path | None:
    env = _normalize_env(env)
    name = SUPPORTED_ENVS[env]
    for base in search_dirs:
        path = Path(base).expanduser() / name
        if path.exists():
            return path.resolve()
    return None


def parse_env_file(path: Path) -> dict[str, str]:
    data: dict[str, str] = {}
    for raw in path.read_text(encoding="utf-8", errors="replace").splitlines():
        line = raw.strip()
        if not line or line.startswith("#"):
            continue
        if "=" not in line:
            continue
        key, value = line.split("=", 1)
        key = key.strip()
        value = value.strip().strip('"').strip("'")
        data[key] = value
    return data


def _api_key(raw: Mapping[str, str]) -> str | None:
    value = (
        raw.get("HL_AGENT_WALLET_ADDRESS", "").strip()
        or raw.get("Mainnet_API_Key", "").strip()
        or raw.get("Testnet_API_Key", "").strip()
        or raw.get("API_Wallet_Adress", "").strip()
        or raw.get("API_Wallet_Address", "").strip()
        or raw.get("API Key", "").strip()
        or raw.get("API_KEY", "").strip()
    )
    return value or None


def _uses_legacy_env_names(raw: Mapping[str, str]) -> bool:
    legacy_keys = {"HL_WALLET_ADDRESS", "HL_API_PRIVATE_KEY", "API_Wallet_Adress", "API_Wallet_Address", "API Key", "API_KEY", "Mainnet_API_Key", "Mainnet_Private_Key", "Mainnet_Account_Address", "Testnet_API_Key", "Testnet_Private_Key", "Testnet_Account_Address"}
    modern_keys = {"HL_ACCOUNT_ADDRESS", "HL_AGENT_PRIVATE_KEY", "HL_AGENT_WALLET_ADDRESS"}
    return any(key in raw for key in legacy_keys) and not modern_keys.issubset(raw.keys())


def derive_address_from_private_key(private_key: str | None) -> str | None:
    if not private_key:
        return None
    try:
        return Account.from_key(private_key).address
    except Exception:
        return None


@dataclass(frozen=True)
class HyperliquidEnvConfig:
    env: str
    env_file_path: Path
    raw: Mapping[str, str]
    account_address: str
    api_private_key: str | None
    api_wallet_address_from_env: str | None
    api_wallet_address_derived: str | None
    errors: tuple[str, ...]
    warnings: tuple[str, ...] = ()
    validation_mode: ValidationMode = "signed"
    allow_api_key_mismatch_for_testnet_probe: bool = False
    allow_mainnet_signed_validation: bool = False

    @property
    def api_wallet_key_matches_private_key(self) -> bool:
        if not self.api_wallet_address_from_env or not self.api_wallet_address_derived:
            return False
        return self.api_wallet_address_from_env.lower() == self.api_wallet_address_derived.lower()

    @property
    def agent_wallet_key_matches_private_key(self) -> bool:
        return self.api_wallet_key_matches_private_key

    @property
    def account_address_equals_api_wallet_address(self) -> bool:
        return bool(self.api_wallet_address_from_env) and self.account_address.lower() == self.api_wallet_address_from_env.lower()

    @property
    def account_address_equals_agent_wallet_address(self) -> bool:
        return self.account_address_equals_api_wallet_address

    @property
    def account_address_equals_derived_signer_address(self) -> bool:
        return bool(self.api_wallet_address_derived) and self.account_address.lower() == self.api_wallet_address_derived.lower()

    @property
    def agent_private_key(self) -> str | None:
        return self.api_private_key

    @property
    def agent_wallet_address_from_env(self) -> str | None:
        return self.api_wallet_address_from_env

    @property
    def agent_wallet_address_derived(self) -> str | None:
        return self.api_wallet_address_derived

    @property
    def sdk_account_address(self) -> str:
        return self.account_address

    @property
    def sdk_secret_key_source(self) -> str | None:
        if not self.api_private_key:
            return None
        return f"HL_API_PRIVATE_KEY in {self.env_file_path.name}"

    @property
    def will_use_mainnet_key(self) -> bool:
        return self.env == "testnet" and self.env_file_path.name != "Testnet.env"

    @property
    def safe_for_readonly(self) -> bool:
        return self.validation_mode == "readonly" and bool(self.account_address) and not self.errors

    @property
    def safe_to_run_testnet_smokes(self) -> bool:
        return self.env == "testnet" and self.validation_mode == "signed" and not self.errors and not self.will_use_mainnet_key and bool(self.api_private_key)

    @property
    def credential_warning(self) -> str | None:
        for preferred in ("api_key_private_key_mismatch", "agent_wallet_private_key_mismatch", "invalid_HL_AGENT_PRIVATE_KEY", "invalid_HL_API_PRIVATE_KEY", "account_address_equals_derived_signer_address"):
            if preferred in self.warnings:
                return preferred
        return None

    def masked(self) -> dict[str, Any]:
        return {
            "env": self.env,
            "env_file_path": str(self.env_file_path),
            "env_file_found": True,
            "validation_mode": self.validation_mode,
            "account_address_masked": mask_address(self.account_address),
            "api_wallet_address_from_env_masked": mask_address(self.api_wallet_address_from_env),
            "agent_wallet_address_from_env_masked": mask_address(self.agent_wallet_address_from_env),
            "api_wallet_address_derived_from_private_key_masked": mask_address(self.api_wallet_address_derived),
            "agent_wallet_address_derived_from_private_key_masked": mask_address(self.agent_wallet_address_derived),
            "api_wallet_key_matches_private_key": self.api_wallet_key_matches_private_key,
            "agent_wallet_key_matches_private_key": self.agent_wallet_key_matches_private_key,
            "account_address_equals_api_wallet_address": self.account_address_equals_api_wallet_address,
            "account_address_equals_agent_wallet_address": self.account_address_equals_agent_wallet_address,
            "account_address_equals_derived_signer_address": self.account_address_equals_derived_signer_address,
            "sdk_account_address_will_be": mask_address(self.sdk_account_address),
            "sdk_secret_key_source": self.sdk_secret_key_source,
            "will_use_mainnet_key": self.will_use_mainnet_key,
            "safe_for_readonly": self.safe_for_readonly,
            "safe_to_run_testnet_smokes": self.safe_to_run_testnet_smokes,
            "credential_warning": self.credential_warning,
            "warnings": list(self.warnings),
            "errors": list(self.errors),
        }


def load_hyperliquid_env(
    env: str,
    *,
    search_dirs: Iterable[str | Path] = DEFAULT_SEARCH_DIRS,
    allow_errors: bool = False,
    validation_mode: ValidationMode = "signed",
    allow_api_key_mismatch_for_testnet_probe: bool = False,
    allow_mainnet_signed_validation: bool = False,
) -> HyperliquidEnvConfig:
    env = _normalize_env(env)
    if validation_mode not in {"readonly", "signed"}:
        raise ValueError(f"unsupported validation_mode: {validation_mode}")
    path = find_env_file(env, search_dirs=search_dirs)
    if path is None:
        raise FileNotFoundError(f"missing_env_file:{SUPPORTED_ENVS[env]}")
    raw = parse_env_file(path)
    errors: list[str] = []
    warnings: list[str] = []
    expected_name = SUPPORTED_ENVS[env]
    if path.name != expected_name:
        errors.append("wrong_env_file_loaded")
    account_address = (raw.get("HL_ACCOUNT_ADDRESS", "").strip() or raw.get("Mainnet_Account_Address", "").strip() or raw.get("Testnet_Account_Address", "").strip() or raw.get("HL_WALLET_ADDRESS", "").strip())
    private_key = (raw.get("HL_AGENT_PRIVATE_KEY", "").strip() or raw.get("Mainnet_Private_Key", "").strip() or raw.get("Testnet_Private_Key", "").strip() or raw.get("HL_API_PRIVATE_KEY", "").strip() or None)
    api_key = _api_key(raw)
    if _uses_legacy_env_names(raw):
        warnings.append("legacy_env_names_used")

    if not account_address:
        errors.extend(["missing_HL_ACCOUNT_ADDRESS", "missing_HL_WALLET_ADDRESS"])

    derived = derive_address_from_private_key(private_key)
    if validation_mode == "signed":
        if env == "mainnet" and not allow_mainnet_signed_validation:
            errors.append("mainnet_signed_actions_blocked")
        if not private_key:
            errors.extend(["missing_HL_AGENT_PRIVATE_KEY", "missing_HL_API_PRIVATE_KEY"])
        elif not derived:
            errors.extend(["invalid_HL_AGENT_PRIVATE_KEY", "invalid_HL_API_PRIVATE_KEY"])
        if api_key and derived and api_key.lower() != derived.lower():
            if env == "testnet" and allow_api_key_mismatch_for_testnet_probe:
                warnings.extend(["agent_wallet_private_key_mismatch", "api_key_private_key_mismatch"])
            else:
                errors.extend(["agent_wallet_private_key_mismatch", "api_key_private_key_mismatch"])
        if not api_key:
            warnings.extend(["missing_HL_AGENT_WALLET_ADDRESS", "missing_API_Key"])
    else:
        if private_key and not derived:
            warnings.extend(["invalid_HL_AGENT_PRIVATE_KEY", "invalid_HL_API_PRIVATE_KEY"])
        if api_key and derived and api_key.lower() != derived.lower():
            warnings.extend(["agent_wallet_private_key_mismatch", "api_key_private_key_mismatch"])
        if not private_key:
            # Read-only intentionally needs no signer; this is diagnostic only.
            pass

    if account_address and api_key and account_address.lower() == api_key.lower():
        errors.extend(["account_address_equals_agent_wallet_address", "account_address_equals_api_wallet_address"])
    if account_address and derived and account_address.lower() == derived.lower():
        # Using the main-wallet private key is not the intended API-wallet isolation for signed tools.
        if validation_mode == "signed":
            errors.append("account_address_equals_derived_signer_address")
        else:
            warnings.append("account_address_equals_derived_signer_address")

    cfg = HyperliquidEnvConfig(
        env=env,
        env_file_path=path,
        raw=raw,
        account_address=account_address,
        api_private_key=private_key,
        api_wallet_address_from_env=api_key,
        api_wallet_address_derived=derived,
        errors=tuple(dict.fromkeys(errors)),
        warnings=tuple(dict.fromkeys(warnings)),
        validation_mode=validation_mode,
        allow_api_key_mismatch_for_testnet_probe=allow_api_key_mismatch_for_testnet_probe,
        allow_mainnet_signed_validation=allow_mainnet_signed_validation,
    )
    if cfg.errors and not allow_errors:
        raise ValueError(";".join(cfg.errors))
    return cfg


def diagnose_hyperliquid_env(
    env: str,
    *,
    search_dirs: Iterable[str | Path] = DEFAULT_SEARCH_DIRS,
    validation_mode: ValidationMode = "signed",
    allow_api_key_mismatch_for_testnet_probe: bool = False,
) -> dict[str, Any]:
    env = _normalize_env(env)
    search_dirs_tuple = tuple(search_dirs)
    path = find_env_file(env, search_dirs=search_dirs_tuple)
    if path is None:
        return {
            "env": env,
            "env_file_path": str(Path(search_dirs_tuple[0] if search_dirs_tuple else DEFAULT_SEARCH_DIRS[-1]).expanduser() / SUPPORTED_ENVS[env]),
            "env_file_found": False,
            "validation_mode": validation_mode,
            "account_address_masked": None,
            "api_wallet_address_from_env_masked": None,
            "agent_wallet_address_from_env_masked": None,
            "api_wallet_address_derived_from_private_key_masked": None,
            "agent_wallet_address_derived_from_private_key_masked": None,
            "api_wallet_key_matches_private_key": False,
            "agent_wallet_key_matches_private_key": False,
            "account_address_equals_api_wallet_address": False,
            "account_address_equals_agent_wallet_address": False,
            "account_address_equals_derived_signer_address": False,
            "sdk_account_address_will_be": None,
            "sdk_secret_key_source": None,
            "will_use_mainnet_key": env == "testnet",
            "safe_for_readonly": False,
            "safe_to_run_testnet_smokes": False,
            "credential_warning": None,
            "warnings": [],
            "errors": ["missing_env_file"],
        }
    try:
        return load_hyperliquid_env(
            env,
            search_dirs=search_dirs_tuple,
            allow_errors=True,
            validation_mode=validation_mode,
            allow_api_key_mismatch_for_testnet_probe=allow_api_key_mismatch_for_testnet_probe,
        ).masked()
    except Exception as exc:
        return {
            "env": env,
            "env_file_path": str(path),
            "env_file_found": True,
            "validation_mode": validation_mode,
            "account_address_masked": None,
            "api_wallet_address_from_env_masked": None,
            "agent_wallet_address_from_env_masked": None,
            "api_wallet_address_derived_from_private_key_masked": None,
            "agent_wallet_address_derived_from_private_key_masked": None,
            "api_wallet_key_matches_private_key": False,
            "agent_wallet_key_matches_private_key": False,
            "account_address_equals_api_wallet_address": False,
            "account_address_equals_agent_wallet_address": False,
            "account_address_equals_derived_signer_address": False,
            "sdk_account_address_will_be": None,
            "sdk_secret_key_source": None,
            "will_use_mainnet_key": env == "testnet" and path.name != "Testnet.env",
            "safe_for_readonly": False,
            "safe_to_run_testnet_smokes": False,
            "credential_warning": None,
            "warnings": [],
            "errors": [type(exc).__name__],
        }
