import argparse
import json
import os
import sqlite3
import urllib.request
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from tweetapi import (
    AuthenticationError,
    NetworkError,
    RateLimitError,
    TweetAPI,
    TweetAPIError,
)


SCHEMA = """
CREATE TABLE IF NOT EXISTS seen_posts (
    post_id TEXT PRIMARY KEY,
    matched_query TEXT NOT NULL,
    author_username TEXT NOT NULL,
    post_text TEXT NOT NULL,
    post_created_at TEXT,
    discovered_at TEXT NOT NULL,
    alert_status TEXT NOT NULL CHECK (
        alert_status IN ('baseline', 'pending', 'sent')
    ),
    alerted_at TEXT
);

CREATE TABLE IF NOT EXISTS query_state (
    query TEXT PRIMARY KEY,
    initialized_at TEXT NOT NULL,
    last_checked_at TEXT NOT NULL
);
"""


def utc_now() -> str:
    return datetime.now(timezone.utc).isoformat()


def open_database(path: Path) -> sqlite3.Connection:
    path.parent.mkdir(parents=True, exist_ok=True)
    connection = sqlite3.connect(path)
    connection.row_factory = sqlite3.Row
    connection.executescript(SCHEMA)
    return connection


def query_is_initialized(connection: sqlite3.Connection, query: str) -> bool:
    row = connection.execute(
        "SELECT 1 FROM query_state WHERE query = ?",
        (query,),
    ).fetchone()
    return row is not None


def remember_results(
    connection: sqlite3.Connection,
    query: str,
    posts: list[dict[str, Any]],
) -> tuple[int, bool]:
    initialized = query_is_initialized(connection, query)
    discovered_at = utc_now()
    new_pending = 0

    with connection:
        for post in posts:
            post_id = str(post.get("id", ""))
            if not post_id:
                continue

            author = post.get("author") or {}
            status = "pending" if initialized else "baseline"
            cursor = connection.execute(
                """
                INSERT OR IGNORE INTO seen_posts (
                    post_id,
                    matched_query,
                    author_username,
                    post_text,
                    post_created_at,
                    discovered_at,
                    alert_status
                ) VALUES (?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    post_id,
                    query,
                    str(author.get("username", "unknown")),
                    str(post.get("text", "")),
                    post.get("createdAt"),
                    discovered_at,
                    status,
                ),
            )
            if cursor.rowcount == 1 and status == "pending":
                new_pending += 1

        connection.execute(
            """
            INSERT INTO query_state (query, initialized_at, last_checked_at)
            VALUES (?, ?, ?)
            ON CONFLICT(query) DO UPDATE SET last_checked_at = excluded.last_checked_at
            """,
            (query, discovered_at, discovered_at),
        )

    return new_pending, not initialized


def format_alert(row: sqlite3.Row) -> str:
    username = row["author_username"]
    text = row["post_text"].replace("\x00", "").strip()
    if len(text) > 700:
        text = text[:697] + "..."

    post_url = f"https://x.com/{username}/status/{row['post_id']}"
    return (
        "New Twitter brand mention\n"
        f"Query: {row['matched_query']}\n"
        f"Author: @{username}\n"
        f"Post: {text}\n"
        f"Link: {post_url}"
    )


def send_telegram_message(token: str, chat_id: str, text: str) -> None:
    request = urllib.request.Request(
        f"https://api.telegram.org/bot{token}/sendMessage",
        data=json.dumps({"chat_id": chat_id, "text": text}).encode("utf-8"),
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    with urllib.request.urlopen(request, timeout=15) as response:
        result = json.loads(response.read().decode("utf-8"))
    if not result.get("ok"):
        raise RuntimeError(result.get("description", "Telegram rejected the alert"))


def deliver_pending_alerts(
    connection: sqlite3.Connection,
    alert_limit: int,
) -> int:
    token = os.environ.get("TELEGRAM_BOT_TOKEN")
    chat_id = os.environ.get("TELEGRAM_CHAT_ID")
    if bool(token) != bool(chat_id):
        raise ValueError(
            "Set both TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID, or neither."
        )

    pending = connection.execute(
        """
        SELECT * FROM seen_posts
        WHERE alert_status = 'pending'
        ORDER BY discovered_at ASC
        LIMIT ?
        """,
        (alert_limit,),
    ).fetchall()

    delivered = 0
    for row in pending:
        message = format_alert(row)
        if token and chat_id:
            send_telegram_message(token, chat_id, message)
        else:
            print("\n" + message + "\n")

        with connection:
            connection.execute(
                """
                UPDATE seen_posts
                SET alert_status = 'sent', alerted_at = ?
                WHERE post_id = ?
                """,
                (utc_now(), row["post_id"]),
            )
        delivered += 1

    return delivered


def run_monitor(
    queries: list[str],
    database_path: Path,
    alert_limit: int,
) -> None:
    client = TweetAPI(api_key=os.environ["TWEETAPI_KEY"])
    connection = open_database(database_path)

    try:
        query_states = [
            (query, query_is_initialized(connection, query))
            for query in queries
        ]
        ordered_queries = [
            query for query, initialized in query_states if initialized
        ] + [
            query for query, initialized in query_states if not initialized
        ]

        for query in ordered_queries:
            response = client.explore.search(query=query, type="Latest")
            posts = response.get("data", [])
            pending_count, was_baseline = remember_results(
                connection,
                query,
                posts,
            )

            if was_baseline:
                print(f"Baseline stored for {query!r}: {len(posts)} results")
            else:
                print(f"Checked {query!r}: {pending_count} new posts queued")

        delivered = deliver_pending_alerts(connection, alert_limit)
        print(f"Delivered {delivered} alerts")
    finally:
        connection.close()


def main() -> None:
    parser = argparse.ArgumentParser(
        description="Search Twitter brand queries and queue new-post alerts."
    )
    parser.add_argument(
        "--query",
        action="append",
        required=True,
        help="Repeat --query to monitor multiple searches",
    )
    parser.add_argument("--database", default="brand-monitor.db")
    parser.add_argument("--alert-limit", type=int, default=20)
    args = parser.parse_args()

    if "TWEETAPI_KEY" not in os.environ:
        parser.error("set TWEETAPI_KEY before running the monitor")
    if args.alert_limit < 1:
        parser.error("--alert-limit must be at least 1")

    queries = [query.strip() for query in args.query if query.strip()]
    if not queries:
        parser.error("at least one non-empty --query is required")

    try:
        run_monitor(
            queries=queries,
            database_path=Path(args.database),
            alert_limit=args.alert_limit,
        )
    except AuthenticationError:
        raise SystemExit("Authentication failed. Check TWEETAPI_KEY.")
    except RateLimitError as error:
        retry_after = getattr(error, "retry_after", None)
        detail = f" Retry after about {retry_after} seconds." if retry_after else ""
        raise SystemExit("Rate limit reached." + detail)
    except NetworkError:
        raise SystemExit("Network request failed after SDK retries.")
    except (OSError, RuntimeError, ValueError, sqlite3.Error) as error:
        raise SystemExit(f"Monitor delivery or storage error: {error}")
    except TweetAPIError as error:
        raise SystemExit(
            f"TweetAPI error {error.status_code}: {error.message}"
        )


if __name__ == "__main__":
    main()
