"""X (Twitter) publisher — posts approved tweets to X.

Handles the full tweet lifecycle:
1. Agent submits draft → output gate blocks system content
2. Draft enters approval_queue (type='tweet')
3. Leo reviews substance → Cory approves via Telegram
4. On approval, this module posts to X via API
5. Records published URL and metrics

Uses Twitter API v2 via OAuth 1.0a for posting.
Read operations still use twitterapi.io (x_client.py).

Epimetheus owns this module.
"""

import json
import hashlib
import hmac
import logging
import sqlite3
import time
import urllib.parse
from pathlib import Path
from typing import Optional

import aiohttp

logger = logging.getLogger("x-publisher")

# ─── Config ──────────────────────────────────────────────────────────

# Twitter API v2 credentials for posting
# OAuth 1.0a keys — stored in separate secret files
_SECRETS_DIR = Path("/opt/teleo-eval/secrets")
_CONSUMER_KEY_FILE = _SECRETS_DIR / "x-consumer-key"
_CONSUMER_SECRET_FILE = _SECRETS_DIR / "x-consumer-secret"
_ACCESS_TOKEN_FILE = _SECRETS_DIR / "x-access-token"
_ACCESS_SECRET_FILE = _SECRETS_DIR / "x-access-secret"

TWITTER_API_V2_URL = "https://api.twitter.com/2/tweets"
REQUEST_TIMEOUT = 15


def _load_secret(path: Path) -> Optional[str]:
    """Load a secret from a file. Returns None if missing."""
    try:
        return path.read_text().strip()
    except Exception:
        return None


def _load_oauth_credentials() -> Optional[dict]:
    """Load all 4 OAuth 1.0a credentials. Returns None if any missing."""
    creds = {
        "consumer_key": _load_secret(_CONSUMER_KEY_FILE),
        "consumer_secret": _load_secret(_CONSUMER_SECRET_FILE),
        "access_token": _load_secret(_ACCESS_TOKEN_FILE),
        "access_secret": _load_secret(_ACCESS_SECRET_FILE),
    }
    missing = [k for k, v in creds.items() if not v]
    if missing:
        logger.warning("Missing X API credentials: %s", ", ".join(missing))
        return None
    return creds


# ─── OAuth 1.0a Signature ────────────────────────────────────────────

def _percent_encode(s: str) -> str:
    return urllib.parse.quote(str(s), safe="")


def _generate_oauth_signature(
    method: str,
    url: str,
    params: dict,
    consumer_secret: str,
    token_secret: str,
) -> str:
    """Generate OAuth 1.0a signature."""
    sorted_params = "&".join(
        f"{_percent_encode(k)}={_percent_encode(v)}"
        for k, v in sorted(params.items())
    )
    base_string = f"{method.upper()}&{_percent_encode(url)}&{_percent_encode(sorted_params)}"
    signing_key = f"{_percent_encode(consumer_secret)}&{_percent_encode(token_secret)}"
    signature = hmac.new(
        signing_key.encode(), base_string.encode(), hashlib.sha1
    ).digest()
    import base64
    return base64.b64encode(signature).decode()


def _build_oauth_header(
    method: str,
    url: str,
    creds: dict,
    extra_params: dict = None,
) -> str:
    """Build the OAuth 1.0a Authorization header."""
    import uuid
    oauth_params = {
        "oauth_consumer_key": creds["consumer_key"],
        "oauth_nonce": uuid.uuid4().hex,
        "oauth_signature_method": "HMAC-SHA1",
        "oauth_timestamp": str(int(time.time())),
        "oauth_token": creds["access_token"],
        "oauth_version": "1.0",
    }

    # Combine oauth params with any extra params for signature
    all_params = {**oauth_params}
    if extra_params:
        all_params.update(extra_params)

    signature = _generate_oauth_signature(
        method, url, all_params,
        creds["consumer_secret"], creds["access_secret"],
    )
    oauth_params["oauth_signature"] = signature

    header_parts = ", ".join(
        f'{_percent_encode(k)}="{_percent_encode(v)}"'
        for k, v in sorted(oauth_params.items())
    )
    return f"OAuth {header_parts}"


# ─── Tweet Submission ────────────────────────────────────────────────

def submit_tweet_draft(
    conn: sqlite3.Connection,
    content: str,
    agent: str,
    context: dict = None,
    reply_to_url: str = None,
    post_type: str = "original",
) -> tuple[int, str]:
    """Submit a tweet draft to the approval queue.

    Returns (request_id, status_message).
    status_message is None on success, error string on failure.

    The output gate and OPSEC filter run before insertion.
    """
    # Import here to avoid circular dependency
    from output_gate import gate_for_tweet_queue
    from approvals import check_opsec

    # Output gate — block system content
    gate = gate_for_tweet_queue(content, agent)
    if not gate:
        return -1, f"Output gate blocked: {', '.join(gate.blocked_reasons)}"

    # OPSEC filter
    opsec_violation = check_opsec(content)
    if opsec_violation:
        return -1, opsec_violation

    # Build context JSON
    ctx = {
        "post_type": post_type,
        "target_account": "TeleoHumanity",  # default, can be overridden
    }
    if reply_to_url:
        ctx["reply_to_url"] = reply_to_url
    if context:
        ctx.update(context)

    # Insert into approval queue
    cursor = conn.execute(
        """INSERT INTO approval_queue
           (type, content, originating_agent, context, leo_review_status,
            expires_at)
           VALUES (?, ?, ?, ?, 'pending_leo',
                   datetime('now', '+24 hours'))""",
        ("tweet", content, agent, json.dumps(ctx)),
    )
    conn.commit()
    request_id = cursor.lastrowid
    logger.info("Tweet draft #%d submitted by %s (%d chars)",
                request_id, agent, len(content))
    return request_id, None


# ─── Tweet Posting ───────────────────────────────────────────────────

async def post_tweet(text: str, reply_to_id: str = None) -> dict:
    """Post a tweet to X via Twitter API v2.

    Returns dict with:
    - success: bool
    - tweet_id: str (if successful)
    - tweet_url: str (if successful)
    - error: str (if failed)
    """
    creds = _load_oauth_credentials()
    if not creds:
        return {"success": False, "error": "X API credentials not configured"}

    # Build request body
    body = {"text": text}
    if reply_to_id:
        body["reply"] = {"in_reply_to_tweet_id": reply_to_id}

    # OAuth 1.0a header (for JSON body, don't include body params in signature)
    auth_header = _build_oauth_header("POST", TWITTER_API_V2_URL, creds)

    headers = {
        "Authorization": auth_header,
        "Content-Type": "application/json",
    }

    try:
        async with aiohttp.ClientSession() as session:
            async with session.post(
                TWITTER_API_V2_URL,
                headers=headers,
                json=body,
                timeout=aiohttp.ClientTimeout(total=REQUEST_TIMEOUT),
            ) as resp:
                result = await resp.json()

                if resp.status == 201:
                    tweet_id = result.get("data", {}).get("id", "")
                    return {
                        "success": True,
                        "tweet_id": tweet_id,
                        "tweet_url": f"https://x.com/TeleoHumanity/status/{tweet_id}",
                    }
                else:
                    error = result.get("detail") or result.get("title") or str(result)
                    logger.error("Tweet post failed (%d): %s", resp.status, error)
                    return {"success": False, "error": f"API error {resp.status}: {error}"}

    except aiohttp.ClientError as e:
        logger.error("Tweet post network error: %s", e)
        return {"success": False, "error": f"Network error: {e}"}


async def post_thread(tweets: list[str]) -> list[dict]:
    """Post a thread (multiple tweets in reply chain).

    Returns list of post results, one per tweet.
    """
    results = []
    reply_to = None

    for i, text in enumerate(tweets):
        result = await post_tweet(text, reply_to_id=reply_to)
        results.append(result)

        if not result["success"]:
            logger.error("Thread posting failed at tweet %d/%d: %s",
                        i + 1, len(tweets), result["error"])
            break

        reply_to = result.get("tweet_id")

    return results


# ─── Post-Approval Hook ─────────────────────────────────────────────

async def handle_approved_tweet(
    conn: sqlite3.Connection,
    request_id: int,
) -> dict:
    """Called when a tweet is approved. Posts to X and records the result.

    Returns the post result dict.
    """
    row = conn.execute(
        "SELECT * FROM approval_queue WHERE id = ? AND type = 'tweet'",
        (request_id,),
    ).fetchone()

    if not row:
        return {"success": False, "error": f"Approval #{request_id} not found"}

    if row["status"] != "approved":
        return {"success": False, "error": f"Approval #{request_id} status is {row['status']}, not approved"}

    content = row["content"]
    ctx = json.loads(row["context"]) if row["context"] else {}

    # Parse thread (tweets separated by ---)
    tweets = [t.strip() for t in content.split("\n---\n") if t.strip()]

    # Extract reply_to tweet ID from URL if present
    reply_to_id = None
    reply_to_url = ctx.get("reply_to_url", "")
    if reply_to_url:
        import re
        match = re.search(r"/status/(\d+)", reply_to_url)
        if match:
            reply_to_id = match.group(1)

    # Post
    if len(tweets) == 1:
        result = await post_tweet(tweets[0], reply_to_id=reply_to_id)
        results = [result]
    else:
        # For threads, first tweet may be a reply
        results = []
        first = await post_tweet(tweets[0], reply_to_id=reply_to_id)
        results.append(first)
        if first["success"] and len(tweets) > 1:
            thread_results = await post_thread(tweets[1:])
            # Fix: thread_results already posted independently, need to chain
            # Actually post_thread handles chaining. Let me re-do this.
            pass
        # Simpler: use post_thread for everything if it's a multi-tweet
        if len(tweets) > 1:
            results = await post_thread(tweets)

    # Record result
    success = all(r["success"] for r in results)
    if success:
        tweet_urls = [r.get("tweet_url", "") for r in results if r.get("tweet_url")]
        published_url = tweet_urls[0] if tweet_urls else ""

        conn.execute(
            """UPDATE approval_queue
               SET context = json_set(COALESCE(context, '{}'),
                   '$.published_url', ?,
                   '$.published_at', datetime('now'),
                   '$.tweet_ids', ?)
               WHERE id = ?""",
            (published_url, json.dumps([r.get("tweet_id") for r in results]), request_id),
        )
        conn.commit()
        logger.info("Tweet #%d published: %s", request_id, published_url)
    else:
        errors = [r.get("error", "unknown") for r in results if not r["success"]]
        conn.execute(
            """UPDATE approval_queue
               SET context = json_set(COALESCE(context, '{}'),
                   '$.post_error', ?,
                   '$.post_attempted_at', datetime('now'))
               WHERE id = ?""",
            ("; ".join(errors), request_id),
        )
        conn.commit()
        logger.error("Tweet #%d post failed: %s", request_id, errors)

    return results[0] if len(results) == 1 else {"success": success, "results": results}
