from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import (
    ApplicationBuilder,
    CommandHandler,
    CallbackQueryHandler,
    MessageHandler,
    filters,
    ContextTypes,
)

import requests
import os
import random
import json
import urllib.parse
from collections import Counter
from datetime import datetime, time, timedelta
from zoneinfo import ZoneInfo

from linkedin_publish import publish_to_linkedin
from twitter_publish import publish_to_twitter, twitter_configured

TELEGRAM_BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "")

SCHEDULE_FILE = "schedule.json"
HISTORY_FILE = "post_history.json"
QUEUE_FILE = "post_queue.json"
PERSONA_FILE = "persona.json"
QUEUE_MAX = 7

TONES = {"formal", "casual", "humorous", "professional"}
LENGTHS = {"short", "long"}

LOCAL_POSTS = [
    "🍎 Swift is not just a language — it's a mindset.\n\nValue types, protocol-oriented design, and safety by default push you to write better code from the start.\n\nOnce you go Swift, verbose languages feel like a step backwards.\n\n#Swift #iOSDev #Apple",
    "🤖 AI is changing how we build mobile apps.\n\nFrom on-device Core ML models to AI-assisted code generation in Xcode, the toolkit has never been more powerful.\n\nThe developers who embrace this shift early will define the next decade of apps.\n\n#AI #iOSDev #CoreML",
    "📱 React Native in 2025 is not the React Native of 2018.\n\nThe new architecture — JSI, Fabric, TurboModules — has transformed performance.\n\nCross-platform no longer means compromising on quality.\n\n#ReactNative #MobileDev #JavaScript",
    "🎉 WWDC is the one week a year where every iOS developer becomes a student again.\n\nNew APIs. New frameworks. New possibilities.\n\nThe energy in the community during WWDC week is unlike anything else in tech.\n\n#WWDC #Apple #iOSDev",
    "🛠️ Xcode keeps getting better — and developers keep underusing it.\n\nLive previews, memory graphs, Swift Package Manager integration, and now AI assistance.\n\nTake time to learn your tools. They compound.\n\n#Xcode #Swift #iOSDev",
    "⚡ SwiftUI has matured into a first-class framework.\n\nAnimations, data flow, navigation — it handles the hard parts elegantly.\n\nIf you haven't revisited it since 2019, you'd be surprised how far it's come.\n\n#SwiftUI #Swift #Apple",
    "🧠 On-device AI is the next big frontier for mobile.\n\nPrivacy-preserving, fast, and offline-capable — models running on the Neural Engine are changing what's possible on iPhone.\n\nCore ML and Create ML make it more accessible than ever.\n\n#AI #CoreML #iOSDev",
    "🚀 The gap between a good app and a great app is often the details.\n\nHaptics. Transitions. Accessibility. Performance.\n\nUsers can't always articulate what makes an app feel premium — but they feel it instantly.\n\n#iOSDev #Swift #MobileDev",
]


# =========================
# SCHEDULE PERSISTENCE
# =========================

def load_schedule():
    if os.path.exists(SCHEDULE_FILE):
        with open(SCHEDULE_FILE, "r") as f:
            return json.load(f)
    return None


def save_schedule(hour: int, minute: int, chat_id: int):
    with open(SCHEDULE_FILE, "w") as f:
        json.dump({"hour": hour, "minute": minute, "chat_id": chat_id}, f)


def delete_schedule():
    if os.path.exists(SCHEDULE_FILE):
        os.remove(SCHEDULE_FILE)


# =========================
# POST HISTORY
# =========================

def load_history() -> list:
    if os.path.exists(HISTORY_FILE):
        with open(HISTORY_FILE, "r") as f:
            return json.load(f)
    return []


def save_to_history(post_text: str, topic: str = ""):
    history = load_history()
    history.append({
        "date": datetime.now(ZoneInfo("UTC")).strftime("%Y-%m-%d %H:%M UTC"),
        "topic": topic or "auto",
        "text": post_text,
    })
    history = history[-50:]
    with open(HISTORY_FILE, "w") as f:
        json.dump(history, f, indent=2)


def get_recent_topics(n: int = 5) -> list:
    history = load_history()
    return [entry["topic"] for entry in history[-n:]]


# =========================
# POST QUEUE
# =========================

def load_queue() -> list:
    if os.path.exists(QUEUE_FILE):
        with open(QUEUE_FILE, "r") as f:
            return json.load(f)
    return []


def save_queue(queue: list):
    with open(QUEUE_FILE, "w") as f:
        json.dump(queue, f, indent=2)


def queue_add(topic: str) -> str:
    queue = load_queue()
    if len(queue) >= QUEUE_MAX:
        return f"Queue is full ({QUEUE_MAX} posts max). Use /queue list or /queue clear."
    queue.append({
        "topic": topic,
        "added": datetime.now(ZoneInfo("UTC")).strftime("%Y-%m-%d %H:%M UTC"),
    })
    save_queue(queue)
    return f"Added to queue ({len(queue)}/{QUEUE_MAX}): {topic}"


def queue_pop() -> dict | None:
    queue = load_queue()
    if not queue:
        return None
    item = queue.pop(0)
    save_queue(queue)
    return item


# =========================
# PERSONA
# =========================

def load_persona() -> str:
    if os.path.exists(PERSONA_FILE):
        with open(PERSONA_FILE, "r") as f:
            data = json.load(f)
            return data.get("bio", "")
    return ""


def save_persona(bio: str):
    with open(PERSONA_FILE, "w") as f:
        json.dump({"bio": bio}, f, indent=2)


def clear_persona():
    if os.path.exists(PERSONA_FILE):
        os.remove(PERSONA_FILE)


# =========================
# PARSE /post ARGS
# =========================

def parse_post_args(args: list) -> tuple:
    tone = ""
    length = ""
    for arg in args:
        a = arg.lower()
        if a in TONES:
            tone = a
        elif a in LENGTHS:
            length = a
    return tone, length


# =========================
# GENERATE POST
# =========================

def call_groq(prompt: str) -> str:
    url = "https://api.groq.com/openai/v1/chat/completions"
    headers = {
        "Authorization": f"Bearer {GROQ_API_KEY}",
        "Content-Type": "application/json",
    }
    payload = {
        "model": "llama-3.1-8b-instant",
        "messages": [{"role": "user", "content": prompt}],
        "temperature": 0.9,
        "max_tokens": 400,
    }
    response = requests.post(url, headers=headers, json=payload, timeout=15)
    data = response.json()
    if "choices" not in data:
        raise RuntimeError(data.get("error", {}).get("message", str(data)))
    return data["choices"][0]["message"]["content"].strip()


def generate_post(topic: str = "", tone: str = "", length: str = "") -> str:
    if GROQ_API_KEY:
        try:
            recent = get_recent_topics(5)
            avoid_hint = (
                f" Do NOT repeat or closely resemble these recent topics: {', '.join(recent)}."
                if recent else ""
            )

            tone_hint = f" Write in a {tone} tone." if tone else ""

            if length == "short":
                length_hint = " Keep it under 100 words."
            elif length == "long":
                length_hint = " Write 250-299 words — detailed, insightful, and engaging."
            else:
                length_hint = " Keep it under 120 words."

            base_topic = topic if topic else (
                "one of: Swift, SwiftUI, AI in mobile development, React Native, Xcode, WWDC, Core ML, or iOS app development"
            )

            persona = load_persona()
            if persona:
                author_hint = f"The author is: {persona}"
            else:
                author_hint = "The author is an iOS/mobile developer passionate about Swift, AI, React Native, and Apple ecosystem."

            prompt = (
                f"Write a LinkedIn post about: {base_topic}."
                f"{tone_hint}{length_hint}{avoid_hint} "
                f"{author_hint} "
                "Rules:\n"
                "- Write like a real person talking to a friend, not a corporate blog\n"
                "- Use simple, everyday words — avoid buzzwords like 'leverage', 'delve', 'game-changer', 'cutting-edge', 'transformative'\n"
                "- Short sentences. Natural rhythm. Occasional incomplete sentences are fine.\n"
                "- Share a genuine opinion or personal observation, not generic advice\n"
                "- It's okay to start with a personal story, honest question, or small frustration\n"
                "- NO motivational fluff or empty inspiration\n"
                "- Structure in short paragraphs with a blank line between each\n"
                "- Add 2 relevant emojis max and 6 hashtags at the very end\n"
                "- Only output the post text, nothing else\n"
                "- Do NOT write about software testing, QA, or Playwright"
            )
            return call_groq(prompt)
        except Exception as e:
            print(f"Groq unavailable ({e}), using local post.")
    return random.choice(LOCAL_POSTS)


# =========================
# IMAGE GENERATION
# =========================

def generate_image_bytes(topic: str) -> bytes:
    prompt = (
        f"Professional LinkedIn post visual about {topic}. "
        "Clean, modern, corporate style. No text. High quality."
    )
    url = (
        f"https://image.pollinations.ai/prompt/{urllib.parse.quote(prompt)}"
        "?width=1200&height=628&nologo=true"
    )
    resp = requests.get(url, timeout=60)
    if resp.status_code != 200:
        raise RuntimeError(f"Image generation failed ({resp.status_code})")
    return resp.content


# =========================
# AUTO-POST JOB
# =========================

async def auto_post_job(context):
    chat_id = context.job.data["chat_id"]
    try:
        queued = queue_pop()
        if queued:
            topic_used = queued["topic"]
            post_text = generate_post(topic=topic_used)
            label = f"Queued topic: {topic_used}"
        else:
            topic_used = "scheduled"
            post_text = generate_post()
            label = "Random auto-post"

        publish_to_linkedin(post_text)
        save_to_history(post_text, topic=topic_used)

        remaining = len(load_queue())
        queue_note = f"\n\n{remaining} post{'s' if remaining != 1 else ''} left in queue." if queued else ""

        await context.bot.send_message(
            chat_id=chat_id,
            text=f"✅ Scheduled post published! ({label})\n\n{post_text}{queue_note}",
        )
    except Exception as e:
        await context.bot.send_message(
            chat_id=chat_id,
            text=f"❗ Scheduled post failed: {e}",
        )


# =========================
# SHARED HELPER
# =========================

def make_keyboard():
    if twitter_configured():
        return InlineKeyboardMarkup([
            [
                InlineKeyboardButton("💼 LinkedIn", callback_data="approve"),
                InlineKeyboardButton("🐦 Twitter", callback_data="approve_twitter"),
            ],
            [
                InlineKeyboardButton("🚀 Both", callback_data="approve_both"),
                InlineKeyboardButton("🔄 Rewrite", callback_data="rewrite"),
            ],
        ])
    return InlineKeyboardMarkup([
        [
            InlineKeyboardButton("✅ Approve", callback_data="approve"),
            InlineKeyboardButton("🔄 Rewrite", callback_data="rewrite"),
        ]
    ])


# =========================
# /POST COMMAND
# =========================

async def post(update: Update, context: ContextTypes.DEFAULT_TYPE):
    tone, length = parse_post_args(context.args or [])

    label_parts = []
    if tone:
        label_parts.append(tone)
    if length:
        label_parts.append(length)
    label = f" ({', '.join(label_parts)})" if label_parts else ""

    if label:
        await update.message.reply_text(f"✍️ Generating{label} post...")

    ai_post = generate_post(tone=tone, length=length)
    context.user_data["approved_post"] = ai_post
    context.user_data["last_topic"] = ""
    context.user_data["last_tone"] = tone
    context.user_data["last_length"] = length
    context.user_data["last_mode"] = "post"

    await update.message.reply_text(ai_post, reply_markup=make_keyboard())


# =========================
# /TOPIC COMMAND
# =========================

async def topic(update: Update, context: ContextTypes.DEFAULT_TYPE):
    if not context.args:
        await update.message.reply_text(
            "Usage: /topic <your topic>\n\nExamples:\n"
            "/topic AI in healthcare\n"
            "/topic remote work productivity\n"
            "/topic Python automation tips"
        )
        return

    user_topic = " ".join(context.args)
    await update.message.reply_text(f"✍️ Generating post about: {user_topic}...")

    ai_post = generate_post(topic=user_topic)
    context.user_data["approved_post"] = ai_post
    context.user_data["last_topic"] = user_topic
    context.user_data["last_tone"] = ""
    context.user_data["last_length"] = ""
    context.user_data["last_mode"] = "post"

    await update.message.reply_text(ai_post, reply_markup=make_keyboard())


# =========================
# PLAIN TEXT → TOPIC
# =========================

async def handle_text(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_topic = update.message.text.strip()
    await update.message.reply_text(f"✍️ Generating post about: {user_topic}...")

    ai_post = generate_post(topic=user_topic)
    context.user_data["approved_post"] = ai_post
    context.user_data["last_topic"] = user_topic
    context.user_data["last_tone"] = ""
    context.user_data["last_length"] = ""
    context.user_data["last_mode"] = "post"

    await update.message.reply_text(ai_post, reply_markup=make_keyboard())


# =========================
# /PERSONA COMMAND
# =========================

async def persona_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    args = context.args or []

    if not args:
        bio = load_persona()
        if bio:
            await update.message.reply_text(
                f"Your current persona:\n\n{bio}\n\n"
                "To update: /persona <your bio>\n"
                "To clear: /persona clear"
            )
        else:
            await update.message.reply_text(
                "No persona set yet.\n\n"
                "Set one so posts sound like YOU:\n"
                "/persona iOS developer with 5 years experience, building SwiftUI apps and exploring on-device AI. I love sharing what I learn.\n\n"
                "To clear: /persona clear"
            )
        return

    if len(args) == 1 and args[0].lower() == "clear":
        clear_persona()
        await update.message.reply_text("✅ Persona cleared. Posts will use the default author style.")
        return

    bio = " ".join(args)
    save_persona(bio)
    await update.message.reply_text(
        f"✅ Persona saved!\n\n\"{bio}\"\n\n"
        "All future posts will now be written in your voice. Try /post to see the difference!"
    )


# =========================
# /HELP COMMAND
# =========================

async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    msg = (
        "Here's everything this bot can do:\n\n"
        "YOUR VOICE\n"
        "/persona — view or set your professional bio\n"
        "/persona <bio> — set your persona for personalised posts\n"
        "/persona clear — reset to default style\n\n"
        "POST GENERATION\n"
        "/post — random LinkedIn post\n"
        "/post formal — formal tone\n"
        "/post casual — casual/friendly tone\n"
        "/post humorous — fun, light-hearted tone\n"
        "/post short — ~100 words\n"
        "/post long — ~250-299 words\n"
        "/post formal long — combine tone + length\n"
        "/topic <subject> — post on a custom topic\n"
        "/imagepost <subject> — post with AI image\n"
        "Just type anything — treated as a topic\n\n"
        "PUBLISHING\n"
        "/preview — see last generated post again\n"
        "/retry — republish last approved post to LinkedIn\n"
        "/quote — generate a punchy one-liner quote\n\n"
        "Every post shows publish buttons:\n"
        "  💼 LinkedIn — post to LinkedIn only\n"
        "  🐦 Twitter — post to Twitter only\n"
        "  🚀 Both — post to LinkedIn + Twitter\n"
        "  🔄 Rewrite — regenerate the post\n\n"
        "POST QUEUE (up to 7 planned ahead)\n"
        "/queue add <topic> — add a topic to the queue\n"
        "/queue list — see all queued topics\n"
        "/queue next — publish the next queued post now\n"
        "/queue remove <number> — remove item by position\n"
        "/queue clear — empty the entire queue\n\n"
        "SCHEDULING\n"
        "/schedule HH:MM — auto-post daily (UTC)\n"
        "/unschedule — stop auto-posting\n\n"
        "INSIGHTS\n"
        "/history — last 5 published posts\n"
        "/stats — top topics + weekly activity\n"
        "/status — schedule info + total post count"
    )
    await update.message.reply_text(msg)


# =========================
# /QUEUE COMMAND
# =========================

async def queue_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    args = context.args or []
    subcommand = args[0].lower() if args else ""

    if subcommand == "add":
        topic_text = " ".join(args[1:]).strip()
        if not topic_text:
            await update.message.reply_text(
                "Usage: /queue add <topic>\nExample: /queue add AI in healthcare"
            )
            return
        msg = queue_add(topic_text)
        await update.message.reply_text(msg)

    elif subcommand == "list":
        queue = load_queue()
        if not queue:
            await update.message.reply_text(
                "Queue is empty. Add topics with /queue add <topic>"
            )
            return
        lines = [f"Queued posts ({len(queue)}/{QUEUE_MAX}) — published oldest first:\n"]
        for i, item in enumerate(queue, 1):
            lines.append(f"{i}. {item['topic']}\n   Added: {item['added']}")
        await update.message.reply_text("\n".join(lines))

    elif subcommand == "next":
        item = queue_pop()
        if not item:
            await update.message.reply_text(
                "Queue is empty. Add topics with /queue add <topic>"
            )
            return
        await update.message.reply_text(f"Generating post for: {item['topic']}...")
        try:
            post_text = generate_post(topic=item["topic"])
            publish_to_linkedin(post_text)
            save_to_history(post_text, topic=item["topic"])
            remaining = len(load_queue())
            queue_note = f"\n\n{remaining} post{'s' if remaining != 1 else ''} left in queue." if remaining else "\n\nQueue is now empty."
            await update.message.reply_text(
                f"✅ Published: {item['topic']}\n\n{post_text}{queue_note}"
            )
        except Exception as e:
            await update.message.reply_text(f"❗ Failed to publish: {e}")

    elif subcommand == "remove":
        if len(args) < 2 or not args[1].isdigit():
            await update.message.reply_text("Usage: /queue remove <number>\nExample: /queue remove 2")
            return
        idx = int(args[1]) - 1
        queue = load_queue()
        if idx < 0 or idx >= len(queue):
            await update.message.reply_text(
                f"No item at position {args[1]}. Use /queue list to see your queue."
            )
            return
        removed = queue.pop(idx)
        save_queue(queue)
        await update.message.reply_text(
            f"Removed: {removed['topic']}\n{len(queue)} item{'s' if len(queue) != 1 else ''} remaining."
        )

    elif subcommand == "clear":
        queue = load_queue()
        if not queue:
            await update.message.reply_text("Queue is already empty.")
            return
        save_queue([])
        await update.message.reply_text(f"Cleared {len(queue)} item{'s' if len(queue) != 1 else ''} from the queue.")

    else:
        queue = load_queue()
        await update.message.reply_text(
            f"Queue commands:\n"
            f"/queue add <topic> — add a topic\n"
            f"/queue list — see all {len(queue)}/{QUEUE_MAX} queued topics\n"
            f"/queue next — publish next item now\n"
            f"/queue remove <number> — remove by position\n"
            f"/queue clear — empty the queue"
        )


# =========================
# /QUOTE COMMAND
# =========================

async def quote(update: Update, context: ContextTypes.DEFAULT_TYPE):
    if GROQ_API_KEY:
        try:
            prompt = (
                "Write one short, honest, original thought (1-2 sentences) "
                "that an iOS developer might actually say to a colleague. "
                "Make it real — not motivational fluff. No buzzwords. "
                "No quotation marks. No attribution. Only output the text."
            )
            q = call_groq(prompt)
            context.user_data["approved_post"] = q
            context.user_data["last_topic"] = ""
            context.user_data["last_tone"] = ""
            context.user_data["last_length"] = ""
            context.user_data["last_mode"] = "quote"
            await update.message.reply_text(q, reply_markup=make_keyboard())
            return
        except Exception as e:
            print(f"Groq unavailable for quote ({e})")
    await update.message.reply_text("The best code is the code you never have to write.")


# =========================
# /RETRY COMMAND
# =========================

async def retry(update: Update, context: ContextTypes.DEFAULT_TYPE):
    last_post = context.user_data.get("approved_post")
    if not last_post:
        records = load_history()
        if records:
            last_post = records[-1]["text"]

    if not last_post:
        await update.message.reply_text(
            "No previous post found. Use /post or type a topic first."
        )
        return

    try:
        publish_to_linkedin(last_post)
        last_topic = context.user_data.get("last_topic", "auto")
        save_to_history(last_post, topic=last_topic)
        await update.message.reply_text("✅ Last post republished to LinkedIn!")
    except Exception as e:
        await update.message.reply_text(f"❗ Failed to republish: {e}")


# =========================
# /PREVIEW COMMAND
# =========================

async def preview(update: Update, context: ContextTypes.DEFAULT_TYPE):
    last_post = context.user_data.get("approved_post")

    if not last_post:
        records = load_history()
        if records:
            last_post = records[-1]["text"]

    if not last_post:
        await update.message.reply_text(
            "No post generated yet. Use /post or type any topic to create one."
        )
        return

    await update.message.reply_text(
        f"Last generated post:\n\n{last_post}",
        reply_markup=make_keyboard(),
    )


# =========================
# /STATS COMMAND
# =========================

async def stats(update: Update, context: ContextTypes.DEFAULT_TYPE):
    records = load_history()

    if not records:
        await update.message.reply_text(
            "No posts published yet. Use /post or type a topic to get started."
        )
        return

    total = len(records)

    skip = {"auto", "scheduled", ""}
    topics = [e["topic"] for e in records if e["topic"] not in skip]
    top_topics = Counter(topics).most_common(5)

    now = datetime.now(ZoneInfo("UTC"))
    week_ago = now - timedelta(days=7)
    this_week = []
    for e in records:
        try:
            dt = datetime.strptime(e["date"], "%Y-%m-%d %H:%M UTC").replace(tzinfo=ZoneInfo("UTC"))
            if dt >= week_ago:
                this_week.append(e)
        except Exception:
            pass

    day_counts = Counter()
    for e in this_week:
        try:
            dt = datetime.strptime(e["date"], "%Y-%m-%d %H:%M UTC").replace(tzinfo=ZoneInfo("UTC"))
            day_counts[dt.strftime("%a %d %b")] += 1
        except Exception:
            pass

    lines = [f"Your LinkedIn posting stats:\n"]
    lines.append(f"Total posts published: {total}")
    lines.append(f"Posts this week: {len(this_week)}\n")

    if top_topics:
        lines.append("Top topics:")
        for t, count in top_topics:
            lines.append(f"  {t} — {count} post{'s' if count > 1 else ''}")
        lines.append("")

    if day_counts:
        lines.append("Activity this week:")
        for day, count in sorted(day_counts.items()):
            bar = "█" * count
            lines.append(f"  {day}: {bar} ({count})")

    await update.message.reply_text("\n".join(lines))


# =========================
# /IMAGEPOST COMMAND
# =========================

async def imagepost(update: Update, context: ContextTypes.DEFAULT_TYPE):
    args = context.args
    user_topic = " ".join(args) if args else ""

    await update.message.reply_text(
        f"🎨 Generating post{' about: ' + user_topic if user_topic else ''}...\n"
        "This takes a few seconds for the image."
    )

    post_text = generate_post(topic=user_topic)
    context.user_data["approved_post"] = post_text
    context.user_data["last_topic"] = user_topic
    context.user_data["last_tone"] = ""
    context.user_data["last_length"] = ""
    context.user_data["last_mode"] = "imagepost"

    try:
        img_bytes = generate_image_bytes(user_topic or "Swift iOS development AI")
        context.user_data["pending_image_bytes"] = img_bytes

        if twitter_configured():
            keyboard = InlineKeyboardMarkup([
                [
                    InlineKeyboardButton("💼 LinkedIn + Image", callback_data="approve_image"),
                    InlineKeyboardButton("🔄 Rewrite", callback_data="rewrite"),
                ],
                [
                    InlineKeyboardButton("🐦 Twitter only", callback_data="approve_twitter"),
                    InlineKeyboardButton("🚀 Both (no image)", callback_data="approve_both"),
                ],
                [
                    InlineKeyboardButton("✅ LinkedIn (no image)", callback_data="approve"),
                ]
            ])
        else:
            keyboard = InlineKeyboardMarkup([
                [
                    InlineKeyboardButton("✅ Approve + Image", callback_data="approve_image"),
                    InlineKeyboardButton("🔄 Rewrite", callback_data="rewrite"),
                ],
                [
                    InlineKeyboardButton("✅ Approve (no image)", callback_data="approve"),
                ]
            ])

        await update.message.reply_photo(
            photo=img_bytes,
            caption=post_text,
            reply_markup=keyboard,
        )
    except Exception as e:
        context.user_data["pending_image_bytes"] = None
        await update.message.reply_text(
            f"{post_text}\n\n⚠️ Image generation failed ({e}). You can still approve as text-only.",
            reply_markup=make_keyboard(),
        )


# =========================
# /HISTORY COMMAND
# =========================

async def history(update: Update, context: ContextTypes.DEFAULT_TYPE):
    records = load_history()
    if not records:
        await update.message.reply_text(
            "No posts published yet. Use /post or type a topic to get started."
        )
        return

    recent = records[-5:][::-1]
    lines = [f"Last {len(recent)} published posts:\n"]
    for i, entry in enumerate(recent, 1):
        preview_text = entry["text"][:80].replace("\n", " ")
        lines.append(f"{i}. [{entry['date']}] ({entry['topic']})\n   {preview_text}...")

    await update.message.reply_text("\n".join(lines))


# =========================
# /SCHEDULE COMMAND
# =========================

async def schedule(update: Update, context: ContextTypes.DEFAULT_TYPE):
    if not context.args:
        await update.message.reply_text(
            "Usage: /schedule HH:MM\nExample: /schedule 09:00\n\nTime is in UTC."
        )
        return

    try:
        hour, minute = map(int, context.args[0].split(":"))
        if not (0 <= hour <= 23 and 0 <= minute <= 59):
            raise ValueError
    except ValueError:
        await update.message.reply_text("❗ Invalid time format. Use HH:MM — e.g. /schedule 09:00")
        return

    chat_id = update.effective_chat.id

    current_jobs = context.job_queue.get_jobs_by_name("auto_post")
    for job in current_jobs:
        job.schedule_removal()

    context.job_queue.run_daily(
        auto_post_job,
        time=time(hour=hour, minute=minute, tzinfo=ZoneInfo("UTC")),
        name="auto_post",
        data={"chat_id": chat_id},
    )

    save_schedule(hour, minute, chat_id)
    await update.message.reply_text(
        f"✅ Scheduled! A LinkedIn post will be auto-published every day at {hour:02d}:{minute:02d} UTC."
    )


# =========================
# /UNSCHEDULE COMMAND
# =========================

async def unschedule(update: Update, context: ContextTypes.DEFAULT_TYPE):
    jobs = context.job_queue.get_jobs_by_name("auto_post")
    if not jobs:
        await update.message.reply_text("No scheduled post is currently active.")
        return

    for job in jobs:
        job.schedule_removal()
    delete_schedule()
    await update.message.reply_text("✅ Scheduled posting has been cancelled.")


# =========================
# /STATUS COMMAND
# =========================

async def status(update: Update, context: ContextTypes.DEFAULT_TYPE):
    saved = load_schedule()
    jobs = context.job_queue.get_jobs_by_name("auto_post")
    history_count = len(load_history())

    if jobs and saved:
        h, m = saved["hour"], saved["minute"]
        await update.message.reply_text(
            f"Auto-post: active — daily at {h:02d}:{m:02d} UTC\n"
            f"Total posts published: {history_count}\n\n"
            f"Use /unschedule to stop or /history to see past posts."
        )
    else:
        await update.message.reply_text(
            f"Auto-post: not active\n"
            f"Total posts published: {history_count}\n\n"
            f"Use /schedule HH:MM to set a daily time."
        )


# =========================
# BUTTONS
# =========================

async def buttons(update: Update, context: ContextTypes.DEFAULT_TYPE):
    query = update.callback_query
    await query.answer()

    if query.data == "rewrite":
        last_mode = context.user_data.get("last_mode", "post")
        last_topic = context.user_data.get("last_topic", "")
        last_tone = context.user_data.get("last_tone", "")
        last_length = context.user_data.get("last_length", "")

        if last_mode == "quote":
            try:
                prompt = (
                    "Write one short, honest, original thought (1-2 sentences) "
                    "that an iOS developer might actually say to a colleague. "
                    "Make it real — not motivational fluff. No buzzwords. "
                    "No quotation marks. No attribution. Only output the text."
                )
                new_post = call_groq(prompt)
            except Exception:
                new_post = "The best code is the code you never have to write."
            context.user_data["approved_post"] = new_post
            await query.message.reply_text(new_post, reply_markup=make_keyboard())

        elif last_mode == "imagepost":
            await query.message.reply_text("🔄 Regenerating post and image...")
            new_post = generate_post(topic=last_topic)
            context.user_data["approved_post"] = new_post
            try:
                img_bytes = generate_image_bytes(last_topic or "Swift iOS development AI")
                context.user_data["pending_image_bytes"] = img_bytes
                keyboard = InlineKeyboardMarkup([
                    [
                        InlineKeyboardButton("✅ Approve + Image", callback_data="approve_image"),
                        InlineKeyboardButton("🔄 Rewrite", callback_data="rewrite"),
                    ],
                    [
                        InlineKeyboardButton("✅ Approve (no image)", callback_data="approve"),
                    ]
                ])
                await query.message.reply_photo(photo=img_bytes, caption=new_post, reply_markup=keyboard)
            except Exception as e:
                context.user_data["pending_image_bytes"] = None
                await query.message.reply_text(
                    f"{new_post}\n\n⚠️ Image generation failed ({e}). Approve as text-only.",
                    reply_markup=make_keyboard(),
                )

        else:
            new_post = generate_post(topic=last_topic, tone=last_tone, length=last_length)
            context.user_data["approved_post"] = new_post
            await query.message.reply_text(new_post, reply_markup=make_keyboard())

    elif query.data == "approve":
        final_post = context.user_data.get("approved_post")
        last_topic = context.user_data.get("last_topic", "")
        try:
            publish_to_linkedin(final_post)
            save_to_history(final_post, topic=last_topic or "auto")
            await query.message.reply_text("✅ Published to LinkedIn!")
        except Exception as e:
            await query.message.reply_text(f"❗ LinkedIn failed: {e}")

    elif query.data == "approve_twitter":
        final_post = context.user_data.get("approved_post")
        last_topic = context.user_data.get("last_topic", "")
        try:
            url = publish_to_twitter(final_post)
            save_to_history(final_post, topic=last_topic or "auto")
            await query.message.reply_text(f"🐦 Published to Twitter!\n{url}")
        except Exception as e:
            await query.message.reply_text(f"❗ Twitter failed: {e}")

    elif query.data == "approve_both":
        final_post = context.user_data.get("approved_post")
        last_topic = context.user_data.get("last_topic", "")
        results = []
        try:
            publish_to_linkedin(final_post)
            results.append("✅ LinkedIn: published")
        except Exception as e:
            results.append(f"❗ LinkedIn failed: {e}")
        try:
            url = publish_to_twitter(final_post)
            results.append(f"🐦 Twitter: {url}")
        except Exception as e:
            results.append(f"❗ Twitter failed: {e}")
        save_to_history(final_post, topic=last_topic or "auto")
        await query.message.reply_text("\n".join(results))

    elif query.data == "approve_image":
        final_post = context.user_data.get("approved_post")
        last_topic = context.user_data.get("last_topic", "")
        img_bytes = context.user_data.get("pending_image_bytes")
        try:
            publish_to_linkedin(final_post, image_bytes=img_bytes)
            save_to_history(final_post, topic=last_topic or "auto")
            await query.message.reply_text("✅ LinkedIn post with image published!")
        except Exception as e:
            await query.message.reply_text(f"❗ Failed to publish: {e}")


# =========================
# START BOT
# =========================

async def post_init(application):
    saved = load_schedule()
    if saved:
        h, m, chat_id = saved["hour"], saved["minute"], saved["chat_id"]
        application.job_queue.run_daily(
            auto_post_job,
            time=time(hour=h, minute=m, tzinfo=ZoneInfo("UTC")),
            name="auto_post",
            data={"chat_id": chat_id},
        )
        print(f"Restored schedule: daily at {h:02d}:{m:02d} UTC for chat {chat_id}")


async def error_handler(update, context):
    import traceback
    from telegram.error import Conflict, NetworkError, TimedOut
    err = context.error
    if isinstance(err, Conflict):
        print(f"[CONFLICT] Another bot instance is running. This instance will keep retrying.")
        return
    if isinstance(err, (NetworkError, TimedOut)):
        print(f"[NETWORK] Transient error: {err}")
        return
    print(f"[ERROR] Unhandled exception: {traceback.format_exc()}")


app = ApplicationBuilder().token(TELEGRAM_BOT_TOKEN).post_init(post_init).build()
app.add_error_handler(error_handler)

app.add_handler(CommandHandler("help", help_command))
app.add_handler(CommandHandler("persona", persona_command))
app.add_handler(CommandHandler("post", post))
app.add_handler(CommandHandler("topic", topic))
app.add_handler(CommandHandler("imagepost", imagepost))
app.add_handler(CommandHandler("quote", quote))
app.add_handler(CommandHandler("retry", retry))
app.add_handler(CommandHandler("queue", queue_command))
app.add_handler(CommandHandler("preview", preview))
app.add_handler(CommandHandler("stats", stats))
app.add_handler(CommandHandler("history", history))
app.add_handler(CommandHandler("schedule", schedule))
app.add_handler(CommandHandler("unschedule", unschedule))
app.add_handler(CommandHandler("status", status))
app.add_handler(CallbackQueryHandler(buttons))
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_text))

print("Bot running...")
app.run_polling(drop_pending_updates=True, allowed_updates=["message", "callback_query"])
