"""obol retry - retry sending failed events.

When the Obol API is unreachable, failed events are saved to
~/.obol/failed_events.jsonl. This command retries sending them.

    obol retry              Retry all failed events
    obol retry --dry-run    Show what would be retried without sending
"""
import base64
import json
import os
import urllib.error
import urllib.request


FAILED_EVENTS_PATH = os.path.expanduser("~/.obol/failed_events.jsonl")

_HMAC_PREFIX = "obol_hmac:"
_GCM_PREFIX = "obol_gcm:"


def _load_archive_key() -> str:
    """Load archive key from ~/.obol/archive_key if it exists."""
    path = os.path.expanduser("~/.obol/archive_key")
    try:
        if os.path.exists(path):
            with open(path) as f:
                return f.read().strip()
    except OSError:
        pass
    return ""


def _decrypt_line(line: str, api_key: str) -> str:
    """Decrypt a failed event line. Handles AES-256-GCM, HMAC, and plaintext."""
    if not line:
        return line
    archive_key = _load_archive_key()
    if not api_key and not archive_key:
        return line
    # AES-256-GCM encrypted lines (current format)
    if line.startswith(_GCM_PREFIX):
        try:
            from cryptography.hazmat.primitives.hashes import SHA256
            from cryptography.hazmat.primitives.kdf.hkdf import HKDF
            from cryptography.hazmat.primitives.ciphers.aead import AESGCM
            raw = base64.urlsafe_b64decode(line[len(_GCM_PREFIX):])
            nonce = raw[:12]
            ct_with_tag = raw[12:]
            # Try archive key first
            if archive_key:
                try:
                    arc_raw = base64.urlsafe_b64decode(archive_key.encode())
                    gcm_key = HKDF(
                        algorithm=SHA256(), length=32,
                        salt=b"obol-archive-aes256-v1", info=b"local-log-encryption",
                    ).derive(arc_raw)
                    return AESGCM(gcm_key).decrypt(nonce, ct_with_tag, None).decode()
                except Exception:
                    pass
            # Try api_key
            if api_key:
                try:
                    gcm_key = HKDF(
                        algorithm=SHA256(), length=32,
                        salt=b"obol-apikey-aes256-v1", info=b"local-log-encryption",
                    ).derive(api_key.encode())
                    return AESGCM(gcm_key).decrypt(nonce, ct_with_tag, None).decode()
                except Exception:
                    pass
        except Exception:
            pass
        return line
    if line.startswith(_HMAC_PREFIX):
        try:
            rest = line[len(_HMAC_PREFIX):]
            sig, encoded = rest.split(":", 1)
            return base64.b64decode(encoded).decode()
        except Exception:
            return line
    return line


CONFIG_PATH = os.path.expanduser("~/.obol/config.json")


def _load_config():
    """Load API key and URL from ~/.obol/config.json."""
    if os.path.exists(CONFIG_PATH):
        try:
            with open(CONFIG_PATH) as f:
                return json.load(f)
        except Exception:
            return {}
    return {}


def _send_event(event, api_key, api_url):
    """POST a single event to the Obol API. Returns True on success."""
    body = json.dumps(event).encode()
    req = urllib.request.Request(
        f"{api_url.rstrip('/')}/events",
        data=body,
        headers={
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
            "User-Agent": "obol-sdk/0.1.0",
        },
        method="POST",
    )
    try:
        urllib.request.urlopen(req, timeout=10)
        return True
    except Exception:
        return False


def cmd_retry(args):
    """Retry sending failed events to the Obol API."""
    dry_run = getattr(args, "dry_run", False)

    # Load failed events
    if not os.path.exists(FAILED_EVENTS_PATH):
        print("No failed events to retry.")
        return

    try:
        with open(FAILED_EVENTS_PATH) as f:
            lines = f.readlines()
    except Exception as e:
        print(f"Error reading {FAILED_EVENTS_PATH}: {e}")
        return

    # Load config for decryption key
    config = _load_config()
    retry_api_key = os.environ.get("OBOL_API_KEY") or config.get("api_key", "")

    events = []
    for line in lines:
        line = line.strip()
        if not line:
            continue
        try:
            decrypted = _decrypt_line(line, retry_api_key)
            events.append(json.loads(decrypted))
        except (json.JSONDecodeError, Exception):
            continue

    if not events:
        print("No failed events to retry.")
        # Clean up empty file
        try:
            os.remove(FAILED_EVENTS_PATH)
        except Exception:
            pass
        return

    if dry_run:
        print(f"Would retry {len(events)} failed event(s):\n")
        for i, event in enumerate(events, 1):
            action = event.get("action", "?")
            agent_id = event.get("agent_id", "?")
            attempted = event.get("attempted_at", "?")
            error = event.get("error", "?")
            print(f"  {i}. agent={agent_id}  action={action}")
            print(f"     failed at: {attempted}")
            print(f"     error: {error}")
            print()
        return

    # Load config for API credentials
    config = _load_config()
    api_key = os.environ.get("OBOL_API_KEY") or config.get("api_key", "")
    api_url = os.environ.get("OBOL_API_URL") or config.get(
        "api_url", "https://obol.grayowlresearch.com/v1"
    )

    if not api_key:
        print("Error: No API key. Set OBOL_API_KEY or run: obol config set api_key <key>")
        return

    succeeded = 0
    still_failed = []

    for event in events:
        # Remove failure metadata before resending
        clean_event = {k: v for k, v in event.items()
                       if k not in ("attempted_at", "error")}

        if _send_event(clean_event, api_key, api_url):
            succeeded += 1
        else:
            still_failed.append(event)

    # Rewrite file with only still-failed events, or delete if all succeeded
    if still_failed:
        try:
            with open(FAILED_EVENTS_PATH, "w") as f:
                for event in still_failed:
                    f.write(json.dumps(event) + "\n")
        except Exception:
            pass
    else:
        try:
            os.remove(FAILED_EVENTS_PATH)
        except Exception:
            pass

    total = len(events)
    failed_count = len(still_failed)
    print(f"Retried {total} events: {succeeded} succeeded, {failed_count} still failing.")


def setup_parser(subparsers):
    """Add retry subparser."""
    parser = subparsers.add_parser("retry", help="Retry sending failed events")
    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="Show what would be retried without sending",
    )
    parser.set_defaults(func=cmd_retry)
