"""Zimbra SOAP/REST API client. No external dependencies — stdlib only."""

import json
import os
import sys
import time
import urllib.request
import urllib.error
import urllib.parse
import ssl

CONFIG_DIR = os.path.expanduser("~/.config/zimba")
CONFIG_FILE = os.path.join(CONFIG_DIR, "config.json")
TOKEN_FILE = os.path.join(CONFIG_DIR, "token.json")

# Accept self-signed certs (common in Zimbra deployments)
SSL_CTX = ssl.create_default_context()
SSL_CTX.check_hostname = False
SSL_CTX.verify_mode = ssl.CERT_NONE


def load_config():
    """Load server URL and credentials from config file."""
    if not os.path.exists(CONFIG_FILE):
        raise RuntimeError(
            f"No config found at {CONFIG_FILE}. Run: zimba setup"
        )
    with open(CONFIG_FILE) as f:
        return json.load(f)


def save_config(server, username, password):
    """Save credentials to config file."""
    os.makedirs(CONFIG_DIR, exist_ok=True)
    config = {"server": server, "username": username, "password": password}
    with open(CONFIG_FILE, "w") as f:
        json.dump(config, f, indent=2)
    os.chmod(CONFIG_FILE, 0o600)

    # Clear cached token
    if os.path.exists(TOKEN_FILE):
        os.remove(TOKEN_FILE)


def _save_token(token, lifetime_ms):
    """Cache auth token with expiry."""
    os.makedirs(CONFIG_DIR, exist_ok=True)
    data = {
        "token": token,
        "expires": time.time() + (lifetime_ms / 1000) - 60,
    }
    with open(TOKEN_FILE, "w") as f:
        json.dump(data, f)
    os.chmod(TOKEN_FILE, 0o600)


def _load_token():
    """Load cached token if still valid."""
    if not os.path.exists(TOKEN_FILE):
        return None
    with open(TOKEN_FILE) as f:
        data = json.load(f)
    if time.time() >= data.get("expires", 0):
        return None
    return data["token"]


def _soap_request(url, namespace, request_name, body, token=None):
    """Make a Zimbra JSON SOAP request."""
    envelope = {
        "Header": {},
        "Body": {
            request_name: {
                "_jsns": namespace,
                **body,
            }
        },
    }
    if token:
        envelope["Header"]["context"] = {
            "_jsns": "urn:zimbra",
            "authToken": {"_content": token},
        }

    data = json.dumps(envelope).encode("utf-8")
    req = urllib.request.Request(
        url,
        data=data,
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    try:
        with urllib.request.urlopen(req, context=SSL_CTX) as resp:
            result = json.loads(resp.read())
            return result.get("Body", result)
    except urllib.error.HTTPError as e:
        error_body = e.read().decode("utf-8", errors="replace")
        try:
            error_json = json.loads(error_body)
            fault = error_json.get("Body", {}).get("Fault", {})
            reason = fault.get("Reason", {}).get("Text", "")
            code = fault.get("Detail", {}).get("Error", {}).get("Code", "")
            raise RuntimeError(f"Zimbra error: {code} — {reason}")
        except (json.JSONDecodeError, KeyError):
            raise RuntimeError(f"HTTP {e.code}: {error_body[:500]}")


def get_token():
    """Get a valid auth token, authenticating if needed."""
    token = _load_token()
    if token:
        return token

    config = load_config()
    soap_url = config["server"].rstrip("/") + "/service/soap"

    result = _soap_request(
        soap_url,
        "urn:zimbraAccount",
        "AuthRequest",
        {
            "account": {"by": "name", "_content": config["username"]},
            "password": {"_content": config["password"]},
        },
    )

    auth_resp = result.get("AuthResponse", {})
    token = auth_resp.get("authToken", [{}])[0].get("_content", "")
    lifetime = auth_resp.get("lifetime", 3600000)

    if not token:
        raise RuntimeError("Authentication failed — no token returned")

    _save_token(token, lifetime)
    return token


def soap(namespace, request_name, body):
    """Make an authenticated SOAP request."""
    config = load_config()
    soap_url = config["server"].rstrip("/") + "/service/soap"
    token = get_token()
    return _soap_request(soap_url, namespace, request_name, body, token)


def upload_attachment(file_path):
    """Upload a file to Zimbra's FileUploadServlet, return the attachment ID."""
    config = load_config()
    token = get_token()

    base = config["server"].rstrip("/")
    url = f"{base}/service/upload?fmt=raw"

    with open(file_path, "rb") as f:
        file_data = f.read()

    filename = os.path.basename(file_path)
    boundary = "----ZimbaBoundary"
    body = (
        f"--{boundary}\r\n"
        f'Content-Disposition: form-data; name="file"; filename="{filename}"\r\n'
        f"Content-Type: application/octet-stream\r\n"
        f"\r\n"
    ).encode("utf-8") + file_data + f"\r\n--{boundary}--\r\n".encode("utf-8")

    req = urllib.request.Request(url, data=body, method="POST")
    req.add_header("Cookie", f"ZM_AUTH_TOKEN={token}")
    req.add_header("Content-Type", f"multipart/form-data; boundary={boundary}")

    with urllib.request.urlopen(req, context=SSL_CTX) as resp:
        text = resp.read().decode("utf-8").strip()
        # Response format: 200,'null','aid'
        if "," in text:
            parts = text.split(",")
            aid = parts[-1].strip().strip("'\"")
        else:
            aid = text.strip("'\"")
        return aid


def download_attachment(msg_id, part, dest_path):
    """Download an email attachment to a local file."""
    config = load_config()
    token = get_token()

    base = config["server"].rstrip("/")
    url = f"{base}/service/home/~/?id={msg_id}&part={part}&auth=co"

    req = urllib.request.Request(url)
    req.add_header("Cookie", f"ZM_AUTH_TOKEN={token}")

    with urllib.request.urlopen(req, context=SSL_CTX) as resp:
        os.makedirs(os.path.dirname(os.path.abspath(dest_path)), exist_ok=True)
        with open(dest_path, "wb") as f:
            f.write(resp.read())
    return dest_path


def get_server():
    """Return the configured server URL."""
    return load_config()["server"].rstrip("/")


def get_username():
    """Return the configured username."""
    return load_config()["username"]
