#!/usr/bin/env python3
"""
Facebook Auto-Poster v2 — ReceptionistAI / AgileAdapt
Reads from content calendar, posts at optimal AEST times,
logs results to PostgreSQL via Elestio.

Upgrades over v1:
  - Reads posts from CONTENT_CALENDAR structure (posts/ directory)
  - Schedules at optimal AEST times: 8am, 12pm, 6pm
  - Tracks posted content to prevent duplicates (PostgreSQL log)
  - Cookie-based auth (bypasses login — more reliable)
  - Per-brand page targeting via direct page ID navigation

Usage:
  python facebook_autoposter_v2.py --brand receptionistai --post-now
  python facebook_autoposter_v2.py --brand receptionistai --schedule
  python facebook_autoposter_v2.py --brand agileadapt --post-now --post-file path/to/post.md

Requirements:
  pip install playwright psycopg2-binary pytz
  playwright install chromium
"""

import os
import sys
import re
import json
import time
import argparse
import random
from datetime import datetime, timedelta
from pathlib import Path
from zoneinfo import ZoneInfo

# ─── Path Setup ───────────────────────────────────────────────────────────────
sys.path.insert(0, "E:/genesis-system/data/genesis-memory")

# ─── Configuration ────────────────────────────────────────────────────────────

AEST = ZoneInfo("Australia/Sydney")

# Optimal posting times (AEST)
OPTIMAL_TIMES = [
    {"hour": 8,  "minute": 0,  "label": "morning"},
    {"hour": 12, "minute": 0,  "label": "midday"},
    {"hour": 18, "minute": 0,  "label": "evening"},
]

# Brand configuration
BRAND_CONFIG = {
    "receptionistai": {
        "page_id": "61586913940075",  # ReceptionistAI Facebook Page ID
        "page_name": "Receptionist AI",
        "content_dir": Path("E:/genesis-system/AGILEADAPT/marketing/posts"),
        "post_prefix": "fb_receptionistai_",
    },
    "agileadapt": {
        "page_id": None,  # TODO: Add AgileAdapt Facebook Page ID
        "page_name": "AgileAdapt",
        "content_dir": Path("E:/genesis-system/AGILEADAPT/marketing/posts"),
        "post_prefix": "fb_agileadapt_",
    },
}

# Facebook credentials (cookie-based auth — more reliable)
FB_C_USER = os.getenv("FB_C_USER", "")  # Set via env or pass as CLI arg
FB_XS = os.getenv("FB_XS", "")          # Set via env or pass as CLI arg

# Elestio PostgreSQL connection (for posting log)
POSTGRES_LOG_TABLE = "facebook_post_log"

# ─── Content Parser ───────────────────────────────────────────────────────────

def parse_post_file(filepath: Path) -> dict:
    """
    Parse a post markdown file.
    Expects format:
      # Post Title
      **Platform:** Facebook
      **Topic:** ...
      ---
      [post content]
    Returns dict with title, platform, topic, content.
    """
    try:
        text = filepath.read_text(encoding="utf-8")
        result = {"filepath": str(filepath), "raw": text}

        # Extract metadata
        for field in ["Platform", "Brand", "Topic", "Generated"]:
            match = re.search(rf"\*\*{field}:\*\*\s*(.+)", text)
            if match:
                result[field.lower()] = match.group(1).strip()

        # Extract content (everything after ---)
        parts = text.split("---", 1)
        if len(parts) > 1:
            result["content"] = parts[1].strip()
        else:
            # Fallback: use full text minus metadata lines
            lines = [l for l in text.split("\n") if not l.startswith("#") and not l.startswith("**")]
            result["content"] = "\n".join(lines).strip()

        return result
    except Exception as e:
        print(f"Error parsing {filepath}: {e}")
        return None


def get_next_unposted(brand: str, posted_ids: set) -> dict:
    """Find the next post file that hasn't been posted yet."""
    config = BRAND_CONFIG[brand]
    content_dir = config["content_dir"]
    prefix = config["post_prefix"]

    post_files = sorted(content_dir.glob(f"{prefix}*.md"))

    for f in post_files:
        if f.stem not in posted_ids:
            parsed = parse_post_file(f)
            if parsed and parsed.get("content"):
                return {"id": f.stem, "file": f, "data": parsed}

    # If all pre-written posts used, pull from generated/
    generated_dir = content_dir / "generated"
    if generated_dir.exists():
        gen_files = sorted(generated_dir.glob("receptionistai_facebook_*.md" if brand == "receptionistai" else "agileadapt_facebook_*.md"))
        for f in gen_files:
            if f.stem not in posted_ids:
                parsed = parse_post_file(f)
                if parsed and parsed.get("content"):
                    return {"id": f.stem, "file": f, "data": parsed}

    return None

# ─── PostgreSQL Log ───────────────────────────────────────────────────────────

def get_posted_ids(brand: str) -> set:
    """
    Retrieve IDs of already-posted content from PostgreSQL.
    Falls back to local JSON log if Postgres unavailable.
    """
    local_log = Path(f"E:/genesis-system/data/facebook_post_log_{brand}.json")

    try:
        from elestio_config import PostgresConfig
        import psycopg2

        conn = psycopg2.connect(**PostgresConfig.get_connection_params())
        cur = conn.cursor()
        cur.execute(
            f"SELECT post_id FROM {POSTGRES_LOG_TABLE} WHERE brand = %s",
            (brand,)
        )
        rows = cur.fetchall()
        conn.close()
        return {r[0] for r in rows}

    except ImportError:
        print("Warning: Elestio config not found. Using local JSON log.")
    except Exception as e:
        print(f"Warning: Could not connect to PostgreSQL: {e}. Using local JSON log.")

    # Fallback: local JSON
    if local_log.exists():
        data = json.loads(local_log.read_text())
        return set(data.get("posted_ids", []))
    return set()


def log_post(brand: str, post_id: str, post_text: str, success: bool, error: str = None):
    """Log posting result to PostgreSQL and local JSON."""
    timestamp = datetime.now(AEST).isoformat()
    record = {
        "post_id": post_id,
        "brand": brand,
        "posted_at": timestamp,
        "success": success,
        "error": error,
        "preview": post_text[:100] if post_text else "",
    }

    # Try PostgreSQL
    try:
        from elestio_config import PostgresConfig
        import psycopg2

        conn = psycopg2.connect(**PostgresConfig.get_connection_params())
        cur = conn.cursor()
        cur.execute(
            f"""
            INSERT INTO {POSTGRES_LOG_TABLE}
              (post_id, brand, posted_at, success, error_msg, preview)
            VALUES (%s, %s, %s, %s, %s, %s)
            ON CONFLICT (post_id, brand) DO UPDATE
              SET posted_at = EXCLUDED.posted_at,
                  success = EXCLUDED.success,
                  error_msg = EXCLUDED.error_msg
            """,
            (post_id, brand, timestamp, success, error, record["preview"])
        )
        conn.commit()
        conn.close()
        print(f"Logged to PostgreSQL: {post_id}")
    except Exception as e:
        print(f"PostgreSQL log failed: {e} — saving to local JSON")

    # Always save local JSON backup
    local_log = Path(f"E:/genesis-system/data/facebook_post_log_{brand}.json")
    existing = json.loads(local_log.read_text()) if local_log.exists() else {"posted_ids": [], "records": []}
    if post_id not in existing["posted_ids"] and success:
        existing["posted_ids"].append(post_id)
    existing["records"].append(record)
    local_log.write_text(json.dumps(existing, indent=2))

# ─── Playwright Poster ────────────────────────────────────────────────────────

def post_to_facebook(post_text: str, page_id: str, c_user: str, xs: str) -> dict:
    """
    Post content to a Facebook Business Page using Playwright.
    Uses cookie-based authentication (most reliable method).
    Returns dict: {success: bool, url: str, error: str}
    """
    from playwright.sync_api import sync_playwright

    result = {"success": False, "url": None, "error": None}
    screenshot_dir = Path("E:/genesis-system/data/screenshots")
    screenshot_dir.mkdir(parents=True, exist_ok=True)

    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        context = browser.new_context(
            user_agent="Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36",
            viewport={"width": 375, "height": 812},
        )

        # Inject session cookies
        context.add_cookies([
            {"name": "c_user", "value": c_user, "domain": ".facebook.com", "path": "/"},
            {"name": "xs",     "value": xs,     "domain": ".facebook.com", "path": "/"},
        ])

        page = context.new_page()

        try:
            # Verify login
            page.goto("https://mbasic.facebook.com/", timeout=15000)
            if "login" in page.url or page.is_visible("input[name='email']"):
                result["error"] = "Cookie auth failed — session expired"
                page.screenshot(path=str(screenshot_dir / "fb_auth_fail.png"))
                return result

            # Navigate to the business page
            target_url = f"https://mbasic.facebook.com/profile.php?id={page_id}"
            page.goto(target_url, timeout=15000)
            page.wait_for_load_state("networkidle", timeout=10000)

            # Handle profile switch if needed
            for switch_selector in ["//div[text()='Switch']", "//*[text()='Switch']", "//input[@value='Switch']"]:
                if page.is_visible(switch_selector):
                    page.click(switch_selector)
                    page.wait_for_load_state("networkidle", timeout=8000)
                    break

            # Dismiss app prompts
            for dismiss_selector in ["//*[text()='Not now']", "//*[text()='Skip']"]:
                if page.is_visible(dismiss_selector):
                    page.click(dismiss_selector)
                    time.sleep(1)
                    break

            # Find and fill the post box
            post_box_found = False

            # Try textarea first (mbasic)
            for selector in ["textarea[name='xc_message']", "textarea", "div[role='textbox']"]:
                if page.is_visible(selector):
                    if selector == "div[role='textbox']":
                        page.click(selector)
                        # Type with human-like pacing
                        for chunk in [post_text[i:i+50] for i in range(0, len(post_text), 50)]:
                            page.keyboard.type(chunk)
                            time.sleep(random.uniform(0.05, 0.15))
                    else:
                        page.fill(selector, post_text)
                    post_box_found = True
                    break

            if not post_box_found:
                page.screenshot(path=str(screenshot_dir / f"fb_nobox_{int(time.time())}.png"))
                result["error"] = "Could not find post input box"
                return result

            time.sleep(1)

            # Click POST button
            post_selectors = [
                "input[name='view_post']",
                "div[aria-label='Post']",
                "button:has-text('POST')",
                "div[role='button']:has-text('POST')",
                "//div[text()='POST']",
            ]

            posted = False
            for selector in post_selectors:
                if page.is_visible(selector):
                    page.click(selector)
                    page.wait_for_load_state("networkidle", timeout=8000)
                    time.sleep(2)
                    posted = True
                    break

            # Confirm publish (mbasic preview step)
            if page.is_visible("input[name='action_publish']"):
                page.click("input[name='action_publish']")
                page.wait_for_load_state("networkidle", timeout=8000)
                time.sleep(2)
                posted = True

            if posted:
                result["success"] = True
                result["url"] = page.url
                page.screenshot(path=str(screenshot_dir / f"fb_success_{int(time.time())}.png"))
            else:
                result["error"] = "Post button not found or click had no effect"
                page.screenshot(path=str(screenshot_dir / f"fb_nopost_{int(time.time())}.png"))

        except Exception as e:
            result["error"] = str(e)
            try:
                page.screenshot(path=str(screenshot_dir / f"fb_error_{int(time.time())}.png"))
            except Exception:
                pass
        finally:
            browser.close()

    return result

# ─── Scheduler ────────────────────────────────────────────────────────────────

def get_next_post_time() -> datetime:
    """Calculate next optimal posting time in AEST."""
    now = datetime.now(AEST)

    for slot in OPTIMAL_TIMES:
        candidate = now.replace(hour=slot["hour"], minute=slot["minute"], second=0, microsecond=0)
        if candidate > now + timedelta(minutes=5):
            return candidate

    # All today's slots passed — use first slot tomorrow
    tomorrow = now + timedelta(days=1)
    first_slot = OPTIMAL_TIMES[0]
    return tomorrow.replace(hour=first_slot["hour"], minute=first_slot["minute"], second=0, microsecond=0)


def wait_until(target: datetime):
    """Wait until target datetime."""
    now = datetime.now(AEST)
    wait_seconds = (target - now).total_seconds()
    if wait_seconds > 0:
        print(f"Waiting {int(wait_seconds)}s until {target.strftime('%Y-%m-%d %H:%M AEST')}...")
        time.sleep(wait_seconds)

# ─── Main ─────────────────────────────────────────────────────────────────────

def main():
    parser = argparse.ArgumentParser(description="Facebook Auto-Poster v2")
    parser.add_argument("--brand", choices=["receptionistai", "agileadapt"], default="receptionistai")
    parser.add_argument("--post-now", action="store_true", help="Post immediately")
    parser.add_argument("--schedule", action="store_true", help="Post at next optimal AEST time")
    parser.add_argument("--post-file", type=str, help="Path to specific post file to use")
    parser.add_argument("--c-user", type=str, help="Facebook c_user cookie")
    parser.add_argument("--xs", type=str, help="Facebook xs cookie")
    parser.add_argument("--dry-run", action="store_true", help="Print post content without posting")
    args = parser.parse_args()

    brand = args.brand
    config = BRAND_CONFIG[brand]

    # Resolve credentials
    c_user = args.c_user or FB_C_USER
    xs = args.xs or FB_XS

    if not args.dry_run and (not c_user or not xs):
        print("Error: Facebook cookies required. Set FB_C_USER and FB_XS env vars, or pass --c-user and --xs")
        sys.exit(1)

    # Get post content
    if args.post_file:
        post_data = parse_post_file(Path(args.post_file))
        if not post_data:
            print(f"Error: Could not parse {args.post_file}")
            sys.exit(1)
        post_text = post_data["content"]
        post_id = Path(args.post_file).stem
    else:
        posted_ids = get_posted_ids(brand)
        next_post = get_next_unposted(brand, posted_ids)
        if not next_post:
            print(f"No unposted content found for {brand}. Generate more posts first.")
            sys.exit(0)
        post_text = next_post["data"]["content"]
        post_id = next_post["id"]

    print(f"\nBrand: {config['page_name']}")
    print(f"Post ID: {post_id}")
    print(f"Preview: {post_text[:100]}...\n")

    if args.dry_run:
        print("--- DRY RUN ---")
        print(post_text)
        print("--- END ---")
        return

    # Scheduling
    if args.schedule:
        next_time = get_next_post_time()
        wait_until(next_time)

    # Post
    print(f"Posting to {config['page_name']} Facebook page...")
    page_id = config["page_id"]
    if not page_id:
        print(f"Error: No page_id configured for {brand}")
        sys.exit(1)

    result = post_to_facebook(post_text, page_id, c_user, xs)

    if result["success"]:
        print(f"Successfully posted!")
        if result["url"]:
            print(f"URL: {result['url']}")
        log_post(brand, post_id, post_text, True)
    else:
        print(f"Posting failed: {result.get('error', 'Unknown error')}")
        log_post(brand, post_id, post_text, False, result.get("error"))
        sys.exit(1)


if __name__ == "__main__":
    main()
