import os, re, json, logging, asyncio, threading
from datetime import datetime, timezone, timedelta
from urllib.parse import quote
from aiohttp import web
from botbuilder.core import BotFrameworkAdapter, BotFrameworkAdapterSettings, TurnContext
from botbuilder.schema import Activity, ActivityTypes, ConversationReference, ChannelAccount, ConversationAccount
from config import CONFIG
from link_resolver import LinkResolver
from db_logger import DBLogger

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[logging.FileHandler("/var/www/html/bot-teams/bot.log"), logging.StreamHandler()])
log = logging.getLogger("GEBot")

SETTINGS = BotFrameworkAdapterSettings(
    app_id=CONFIG["app_id"],
    app_password=CONFIG["app_password"],
    channel_auth_tenant=CONFIG["tenant_id"]
)
ADAPTER = BotFrameworkAdapter(SETTINGS)
RESOLVER = LinkResolver()
DB = DBLogger()

ADMINS = ["Alexander Atanasov", "Natalie Morgan", "Stas", "Matthew"]

# Sales Reports group for daily report
REPORT_CONV_ID = "19:5b7c7598ab8c4149b03f3631b2192dfc@thread.v2"
REPORT_SERVICE_URL = "https://smba.trafficmanager.net/emea/01396794-fc17-421b-8c42-21bbe2f1cf02/"
REPORT_HOUR = 3  # 3:00 AM EET
LOOKUP_URL = "https://customer-lookup.abracadabra.net/find.php?search_text={query}"

async def on_error(context: TurnContext, error: Exception):
    log.error(f"Bot error: {error}", exc_info=True)
ADAPTER.on_turn_error = on_error

HELP_TEXT = """**GE Payment Bot - Help**

**1. Get Payment Link**
Paste the template with client data:

NAME - John Smith
CID 12345
AMOUNT: 499 $
PRODUCTS - D+L
EMAIL: client@email.com
PSP (FANBASIS/WHOP):
LOAN (YES/NO): NO

Available amounts: $99, $125-$2,499, $4,997, $5,000, $7,000, $10,000
If no exact match, bot finds the closest or splits into 2 links.
Manual split: AMOUNT: 5500 $ (2 links 1500 + 1 link 2500)

**2. Check Transaction**
- **check client@email.com** - search by email
- **check John Smith** - search by name
- **check 5551234567** - search by phone (any format works)

**3. Other Commands**
- **help** - show this message
- **stats** - provider distribution (admin only)"""

def parse_manual_split(text):
    m = re.search(r'\((.+?)\)', text)
    if not m: return None
    inner = m.group(1)
    parts = re.findall(r'(\d+)\s*links?\s*(\d[\d,]*)', inner, re.IGNORECASE)
    if not parts: return None
    result = []
    for count_str, amount_str in parts:
        count = int(count_str)
        amount = int(amount_str.replace(",", ""))
        for _ in range(count):
            result.append(amount)
    return result if result else None

def parse_agent_message(text):
    if not text: return None
    text = re.sub(r"<at>.*?</at>", "", text)
    text = re.sub(r"<[^>]+>", "", text)
    text = text.strip()
    result = {}
    m = re.search(r"NAME\s*[-\u2013\u2014:]\s*(.+)", text, re.IGNORECASE)
    if m: result["name"] = m.group(1).strip()
    m = re.search(r"CID\s*[:\s]*(\d+)", text, re.IGNORECASE)
    if m: result["cid"] = m.group(1).strip()
    m = re.search(r"AMOUNT\s*[:\s]*\$?\s*([\d,]+(?:\.\d{1,2})?)\s*\$?", text, re.IGNORECASE)
    if m:
        try: result["amount"] = int(float(m.group(1).replace(",", "")))
        except ValueError: pass
    manual = parse_manual_split(text)
    if manual: result["manual_split"] = manual
    m = re.search(r"PRODUCTS?\s*[-\u2013\u2014:]\s*(.+)", text, re.IGNORECASE)
    if m: result["products"] = m.group(1).strip()
    m = re.search(r"EMAIL\s*[:\s]*(\S+@\S+)", text, re.IGNORECASE)
    if m: result["email"] = m.group(1).strip()
    m = re.search(r"PSP\s*(?:\([^)]*\))?\s*[:\s]*(fanbasis2|fanbasis|whop)", text, re.IGNORECASE)
    if m: result["psp"] = m.group(1).strip().lower()
    m = re.search(r"LOAN\s*(?:\([^)]*\))?\s*[:\s]*(YES|NO|)", text, re.IGNORECASE)
    if m: result["loan"] = m.group(1).strip().upper()
    if "name" in result and "amount" in result:
        return result
    return None

def get_clean_text(text):
    text = re.sub(r"<at>.*?</at>", "", text)
    text = re.sub(r"<[^>]+>", "", text)
    return text.strip().lower()

async def on_message(turn_context: TurnContext):
    if turn_context.activity.type != ActivityTypes.message:
        return
    text = turn_context.activity.text or ""
    agent_name = turn_context.activity.from_property.name or "Unknown"
    agent_id = turn_context.activity.from_property.id or ""
    clean = get_clean_text(text)
    log.info(f"Message from {agent_name}: {clean[:200]}")
    log.info(f"CONV_ID: {turn_context.activity.conversation.id} | SERVICE_URL: {turn_context.activity.service_url}")

    if not clean or clean in ("help", "?"):
        await turn_context.send_activity(Activity(type=ActivityTypes.message, text=HELP_TEXT, reply_to_id=turn_context.activity.id))
        return

    if clean == "stats":
        if agent_name not in ADMINS:
            await turn_context.send_activity(Activity(type=ActivityTypes.message, text="Access denied.", reply_to_id=turn_context.activity.id))
            return
        report = DB.get_daily_report()
        stats = RESOLVER.get_stats()
        lines = []
        lines.append(f"**Daily Report ({report['date']}):**\n")
        lines.append(f"Total links generated: **{report['total']}**")
        lines.append(f"Total amount: **${report['total_amount']:,}**")
        lines.append(f"Unique clients: **{report['unique_clients']}**")
        lines.append("")
        lines.append("**By Provider (today):**")
        for p, cnt in report["by_provider"].items():
            pct = round(cnt / report["total"] * 100, 1) if report["total"] else 0
            lines.append(f"- {p}: {cnt} links ({pct}%)")
        lines.append("")
        lines.append("**By Agent (today):**")
        sorted_agents = sorted(report["by_agent"].items(), key=lambda x: x[1], reverse=True)
        for a, cnt in sorted_agents:
            agent_amount = report["by_agent_amount"].get(a, 0)
            lines.append(f"- {a}: {cnt} links (${agent_amount:,})")
        lines.append("")
        lines.append("**Provider Balance (all time):**")
        for p, s in stats.items():
            lines.append(f"- {p}: {s['count']} total ({s['pct']}%)")
        await turn_context.send_activity(Activity(type=ActivityTypes.message, text="\n".join(lines), reply_to_id=turn_context.activity.id))
        return

    # Check/lookup command
    if clean.startswith("check ") or clean.startswith("lookup "):
        query = clean.split(" ", 1)[1].strip()
        try:
            import aiohttp as aio
            all_results = []
            # Check if it looks like a phone number
            digits_only = re.sub(r'[^0-9]', '', query)
            if len(digits_only) >= 7:
                # Generate phone variants
                queries = set()
                queries.add(query)
                queries.add(digits_only)
                if len(digits_only) == 10:
                    queries.add(f"1{digits_only}")
                    queries.add(f"+1{digits_only}")
                    queries.add(f"{digits_only[:3]}-{digits_only[3:6]}-{digits_only[6:]}")
                    queries.add(f"({digits_only[:3]}) {digits_only[3:6]}-{digits_only[6:]}")
                    queries.add(f"+1 {digits_only[:3]}-{digits_only[3:6]}-{digits_only[6:]}")
                elif len(digits_only) == 11 and digits_only[0] == '1':
                    base = digits_only[1:]
                    queries.add(base)
                    queries.add(f"+1{base}")
                    queries.add(f"{base[:3]}-{base[3:6]}-{base[6:]}")
                    queries.add(f"({base[:3]}) {base[3:6]}-{base[6:]}")
                    queries.add(f"+1 {base[:3]}-{base[3:6]}-{base[6:]}")
                seen_ids = set()
                async with aio.ClientSession() as session:
                    for q in queries:
                        try:
                            async with session.get(LOOKUP_URL.format(query=quote(q)), timeout=aio.ClientTimeout(total=10)) as resp:
                                data = await resp.json()
                            if data.get("success") and data.get("data"):
                                for r in data["data"]:
                                    rd = r.get("rowData", {})
                                    uid = f"{rd.get('Email','')}{rd.get('Date','')}{rd.get('Amount','')}"
                                    if uid not in seen_ids:
                                        seen_ids.add(uid)
                                        all_results.append(r)
                        except: pass
            else:
                async with aio.ClientSession() as session:
                    async with session.get(LOOKUP_URL.format(query=quote(query)), timeout=aio.ClientTimeout(total=10)) as resp:
                        data = await resp.json()
                if data.get("success") and data.get("data"):
                    all_results = data["data"]
            if all_results:
                text_parts = [f"**Found {len(all_results)} transaction(s):**\n"]
                for r in all_results[:10]:
                    rd = r.get("rowData", {})
                    text_parts.append(
                        f"- **{rd.get('Name','N/A')}** | ${rd.get('Amount','0')} | {rd.get('Method','N/A')} | {rd.get('Date','N/A')} | {rd.get('Email','N/A')}"
                    )
                await turn_context.send_activity(Activity(type=ActivityTypes.message, text="\n".join(text_parts), reply_to_id=turn_context.activity.id))
            else:
                await turn_context.send_activity(Activity(type=ActivityTypes.message, text=f"No transactions found for **{query}**", reply_to_id=turn_context.activity.id))
        except Exception as e:
            log.error(f"Lookup error: {e}")
            await turn_context.send_activity(Activity(type=ActivityTypes.message, text="Lookup service error.", reply_to_id=turn_context.activity.id))
        return

    parsed = parse_agent_message(text)
    if not parsed:
        await turn_context.send_activity(Activity(type=ActivityTypes.message, text="Could not parse your request. Type **help** to see the template.", reply_to_id=turn_context.activity.id))
        return

    amount = parsed["amount"]
    client_name = parsed.get("name", "N/A")
    cid = parsed.get("cid", "N/A")
    email = parsed.get("email", "N/A")
    products = parsed.get("products", "N/A")
    manual_split = parsed.get("manual_split")
    forced_psp = parsed.get("psp")

    # Duplicate CID check (last 24h)
    dup_warning = ""
    if cid != "N/A":
        dup = DB.check_duplicate_cid(cid, hours=24)
        if dup:
            dup_warning = f"**Warning:** CID {cid} was already requested {dup['hours_ago']}h ago by {dup['agent_name']} (${dup['amount']:,})\n\n"

    if manual_split:
        links = []
        for sa in manual_split:
            link = RESOLVER.get_link(sa, forced_provider=forced_psp)
            if link: links.append(link)
            else:
                sub = RESOLVER.smart_resolve(sa, forced_provider=forced_psp)
                if sub: links.extend(sub)
        if not links:
            await turn_context.send_activity(Activity(type=ActivityTypes.message, text="Could not resolve manual split.", reply_to_id=turn_context.activity.id))
            return
        total_sum = sum(l["amount"] for l in links)
        note = "Manual split: " + " + ".join(f"${a:,}" for a in manual_split) + f" = ${total_sum:,}"
    else:
        links = RESOLVER.smart_resolve(amount, forced_provider=forced_psp)
        if not links:
            await turn_context.send_activity(Activity(type=ActivityTypes.message, text=f"No link for **${amount:,}**. Type **help** for available amounts.", reply_to_id=turn_context.activity.id))
            return
        note = links[0].get("note") if len(links) == 1 else links[0].get("note", "")

    parts = []
    if dup_warning:
        parts.append(dup_warning)
    if note:
        parts.append(note)
    for l in links:
        parts.append(f"\n**${l['amount']:,}** \u2192 {l['url']}")
    response_text = "\n".join(parts)

    for l in links:
        DB.log_request(agent_name=agent_name, agent_id=agent_id, client_name=client_name, cid=cid, email=email, amount=l["amount"], products=products, provider=l["provider"], link_type=l["type"], url=l["url"])

    await turn_context.send_activity(Activity(type=ActivityTypes.message, text=response_text, reply_to_id=turn_context.activity.id))
    log.info(f"SENT: {agent_name} -> {client_name} (CID:{cid}) ${amount:,} -> {len(links)} link(s)")

async def messages(req: web.Request) -> web.Response:
    if "application/json" not in req.headers.get("Content-Type", ""):
        return web.Response(status=415)
    body = await req.json()
    activity = Activity().deserialize(body)
    auth_header = req.headers.get("Authorization", "")
    try:
        response = await ADAPTER.process_activity(activity, auth_header, on_message)
        if response:
            return web.json_response(data=response.body, status=response.status)
        return web.Response(status=201)
    except Exception as e:
        log.error(f"Error: {e}", exc_info=True)
        return web.Response(status=500)

async def health(req: web.Request) -> web.Response:
    return web.json_response({"status": "ok", "stats": RESOLVER.get_stats(), "total": DB.get_total_count()})

# === DAILY REPORT ===
async def send_daily_report():
    """Send daily summary to Sales Reports group at 3:00 AM EET"""
    eet = timezone(timedelta(hours=2))
    while True:
        now = datetime.now(eet)
        target = now.replace(hour=REPORT_HOUR, minute=0, second=0, microsecond=0)
        if now >= target:
            target += timedelta(days=1)
        wait_secs = (target - now).total_seconds()
        log.info(f"Daily report scheduled in {wait_secs/3600:.1f}h at {target}")
        await asyncio.sleep(wait_secs)

        try:
            report = DB.get_daily_report()
            if report["total"] == 0:
                text = "**Daily Report** - No requests today."
            else:
                text = f"**Daily Report ({report['date']}):**\n\n"
                text += f"Total requests: **{report['total']}**\n"
                text += f"Total amount: **${report['total_amount']:,}**\n"
                text += f"Unique clients: **{report['unique_clients']}**\n\n"
                text += "**By Provider:**\n"
                for p, cnt in report["by_provider"].items():
                    text += f"- {p}: {cnt} requests\n"
                text += "\n**By Agent:**\n"
                for a, cnt in report["by_agent"].items():
                    text += f"- {a}: {cnt} requests\n"

            conv_ref = ConversationReference(
                conversation=ConversationAccount(id=REPORT_CONV_ID),
                service_url=REPORT_SERVICE_URL,
                channel_id="msteams",
                bot=ChannelAccount(id=CONFIG["app_id"], name="GE Payment Bot"),
            )

            async def send_report(turn_context: TurnContext):
                await turn_context.send_activity(Activity(type=ActivityTypes.message, text=text))

            await ADAPTER.continue_conversation(conv_ref, send_report, CONFIG["app_id"])
            log.info(f"Daily report sent: {report['total']} requests")
        except Exception as e:
            log.error(f"Daily report error: {e}", exc_info=True)

async def on_startup(app):
    asyncio.create_task(send_daily_report())

APP = web.Application()
APP.router.add_post("/api/messages", messages)
APP.router.add_get("/health", health)
APP.on_startup.append(on_startup)

if __name__ == "__main__":
    log.info(f"GE Payment Bot starting on port {CONFIG['port']}")
    web.run_app(APP, host="127.0.0.1", port=CONFIG["port"])
