"""
Oasyce Identity Module

Provides a simple Ed25519-based identity for the Oasyce P2P network.
The keypair is stored in ~/.oasyce/identity.json, with the private key
encrypted using AES-GCM (key derived from the system encryption_key).

Note: This is the *local identity* (Ed25519), not the chain wallet
(secp256k1).  The chain wallet lives in ~/.oasyce/wallet.json and is
managed by oasyce-sdk.
"""

from __future__ import annotations

import json
import os
import secrets
from pathlib import Path
from typing import Optional

from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.serialization import (
    Encoding,
    NoEncryption,
    PrivateFormat,
    PublicFormat,
)

from oasyce.crypto.keys import _encrypt_key, _decrypt_key

IDENTITY_PATH = Path.home() / ".oasyce" / "identity.json"
# Legacy path — checked once for automatic migration
_LEGACY_WALLET_PATH = Path.home() / ".oasyce" / "wallet.json"
ENCRYPTION_KEY_PATH = Path.home() / ".oasyce" / "encryption_key"


def _migrate_legacy_wallet() -> None:
    """One-time migration: rename wallet.json → identity.json if it
    contains an Ed25519 identity (version field) rather than a chain
    wallet (mnemonic field)."""
    if IDENTITY_PATH.exists() or not _LEGACY_WALLET_PATH.exists():
        return
    try:
        data = json.loads(_LEGACY_WALLET_PATH.read_text())
        if "version" in data and "public_key" in data and "mnemonic" not in data:
            _LEGACY_WALLET_PATH.rename(IDENTITY_PATH)
    except Exception:
        pass


def _load_or_create_encryption_key() -> str:
    """Load the system encryption key from ~/.oasyce/encryption_key,
    or create one if it does not exist."""
    if ENCRYPTION_KEY_PATH.exists():
        return ENCRYPTION_KEY_PATH.read_text().strip()
    # Auto-generate
    passphrase = secrets.token_hex(32)
    ENCRYPTION_KEY_PATH.parent.mkdir(parents=True, exist_ok=True)
    ENCRYPTION_KEY_PATH.write_text(passphrase)
    os.chmod(str(ENCRYPTION_KEY_PATH), 0o600)
    return passphrase


class Identity:
    """Ed25519 identity for the Oasyce P2P network.

    Wraps a single Ed25519 keypair.  The public key hex serves as the
    local network address (NOT the chain address).
    """

    def __init__(self, private_key_hex: str, public_key_hex: str):
        self._private_key_hex = private_key_hex
        self._public_key_hex = public_key_hex

    # ── Properties ──────────────────────────────────────────

    @property
    def address(self) -> str:
        """Return the public key hex as the identity address."""
        return self._public_key_hex

    @property
    def private_key_hex(self) -> str:
        return self._private_key_hex

    @property
    def public_key_hex(self) -> str:
        return self._public_key_hex

    # ── Factory methods ─────────────────────────────────────

    @staticmethod
    def exists(identity_path: Optional[Path] = None, *, wallet_path: Optional[Path] = None) -> bool:
        """Check whether an identity file exists on disk."""
        _migrate_legacy_wallet()
        path = identity_path or wallet_path or IDENTITY_PATH
        return path.is_file()

    @classmethod
    def create(
        cls,
        identity_path: Optional[Path] = None,
        passphrase: Optional[str] = None,
        *,
        wallet_path: Optional[Path] = None,
    ) -> "Identity":
        """Generate a new Ed25519 keypair and persist to *identity_path*.

        The private key is encrypted with AES-GCM using a passphrase
        derived from the system encryption_key (or an explicit *passphrase*).

        Args:
            identity_path: Where to store the identity JSON
                (default ~/.oasyce/identity.json).
            passphrase: Encryption passphrase.  If ``None``, the system
                encryption_key at ``~/.oasyce/encryption_key`` is used.

        Returns:
            A new :class:`Identity` instance.
        """
        _migrate_legacy_wallet()
        path = identity_path or wallet_path or IDENTITY_PATH
        passphrase = passphrase or _load_or_create_encryption_key()

        # Generate keypair
        private_key = Ed25519PrivateKey.generate()
        priv_bytes = private_key.private_bytes(Encoding.Raw, PrivateFormat.Raw, NoEncryption())
        pub_bytes = private_key.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw)
        priv_hex = priv_bytes.hex()
        pub_hex = pub_bytes.hex()

        # Encrypt and save
        encrypted = _encrypt_key(priv_hex, passphrase)
        path.parent.mkdir(parents=True, exist_ok=True)
        payload = {
            "version": 1,
            "public_key": pub_hex,
            "encrypted_private_key": encrypted.hex(),
        }
        path.write_text(json.dumps(payload, indent=2))
        os.chmod(str(path), 0o600)

        return cls(priv_hex, pub_hex)

    @classmethod
    def load(
        cls,
        identity_path: Optional[Path] = None,
        passphrase: Optional[str] = None,
        *,
        wallet_path: Optional[Path] = None,
    ) -> "Identity":
        """Load an existing identity from disk.

        Args:
            identity_path: Path to identity JSON
                (default ~/.oasyce/identity.json).
            passphrase: Decryption passphrase.  If ``None``, uses the
                system encryption_key.

        Returns:
            An :class:`Identity` instance.

        Raises:
            FileNotFoundError: If the identity file does not exist.
            ValueError: If decryption fails (wrong passphrase).
        """
        _migrate_legacy_wallet()
        path = identity_path or wallet_path or IDENTITY_PATH
        if not path.is_file():
            raise FileNotFoundError(f"Identity not found at {path}")

        passphrase = passphrase or _load_or_create_encryption_key()
        data = json.loads(path.read_text())
        pub_hex = data["public_key"]
        encrypted = bytes.fromhex(data["encrypted_private_key"])
        priv_hex = _decrypt_key(encrypted, passphrase)

        return cls(priv_hex, pub_hex)

    @staticmethod
    def get_address(identity_path: Optional[Path] = None, *, wallet_path: Optional[Path] = None) -> Optional[str]:
        """Return the identity address without decrypting the private key.

        Returns ``None`` if no identity exists.
        """
        _migrate_legacy_wallet()
        path = identity_path or wallet_path or IDENTITY_PATH
        if not path.is_file():
            return None
        try:
            data = json.loads(path.read_text())
            return data.get("public_key")
        except (json.JSONDecodeError, KeyError):
            return None

    # ── Repr ────────────────────────────────────────────────

    def __repr__(self) -> str:
        return f"Identity(address={self.address[:16]}...)"


# Backward compatibility alias — deprecated, will be removed
Wallet = Identity
