import os
import requests
import csv
import time
import argparse

from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

from datetime import datetime, timedelta
from dotenv import load_dotenv
from urllib.parse import urlencode, urlparse, parse_qs
import pandas as pd
import unicodedata
import re
from db import insert_dataframe_ignore
import json
import hashlib

# ============================
# ENV & CONFIG
# ============================

load_dotenv()

PAGE_ID = os.getenv("PAGE_ID")
PAGE_ACCESS_TOKEN = os.getenv("PAGE_ACCESS_TOKEN")
IG_BUSINESS_ID = os.getenv("INSTAGRAM_BUSINESS_ID")

GRAPH_API_BASE = "https://graph.facebook.com/v19.0"

PLATFORM_FACEBOOK = "facebook"
PLATFORM_INSTAGRAM = "instagram"


# ============================
# HELPERS
# ============================
# Reusable retry-enabled HTTP session
session = requests.Session()

retry_strategy = Retry(
    total=3,
    backoff_factor=1,
    status_forcelist=[429, 500, 502, 503, 504],
    allowed_methods=["GET"]
)

adapter = HTTPAdapter(max_retries=retry_strategy)

session.mount("https://", adapter)
session.mount("http://", adapter)

def check_env():
    missing = []
    if not PAGE_ID:
        missing.append("PAGE_ID")
    if not PAGE_ACCESS_TOKEN:
        missing.append("PAGE_ACCESS_TOKEN")
    if not IG_BUSINESS_ID:
        missing.append("IG_BUSINESS_ID")
    if missing:
        raise ValueError(f"Missing env vars: {missing}")

def parse_date(date_str):
    return datetime.strptime(date_str, "%m/%d/%Y").strftime("%Y-%m-%d")

def clean_text(text):
    if not text:
        return ""
    text = unicodedata.normalize("NFKD", str(text))
    return text.encode("utf-8", errors="replace").decode("utf-8")

def is_valid_comment(text):
    if not text:
        return False
    cleaned = str(text).strip()
    if len(cleaned) < 2:
        return False

    return True

# def fb_get(endpoint, params=None):
#     if params is None:
#         params = {}
#     params["access_token"] = PAGE_ACCESS_TOKEN
#     url = f"{GRAPH_API_BASE}/{endpoint}?{urlencode(params)}"
#     resp = requests.get(url)
#     if resp.status_code != 200:
#         raise RuntimeError(resp.text)
#     return resp.json()

def fb_get(endpoint, params=None):

    if params is None:
        params = {}

    params["access_token"] = PAGE_ACCESS_TOKEN

    url = f"{GRAPH_API_BASE}/{endpoint}?{urlencode(params)}"

    try:

        response = session.get(url, timeout=30)

        # Soft rate limiting
        time.sleep(0.2)

        response.raise_for_status()

        return response.json()

    except requests.exceptions.HTTPError:

        print(f"\n[HTTP ERROR]")
        print(f"Endpoint: {endpoint}")
        print(f"Status: {response.status_code}")
        print(f"Response: {response.text}\n")

        return {}

    except Exception as e:

        print(f"\n[REQUEST FAILED]")
        print(f"Endpoint: {endpoint}")
        print(f"Error: {str(e)}\n")

        return {}


# ============================
# EXTRACT ARTICLE ID (if any)
# ============================

def extract_article_id(text):
    if not text:
        return None

    # Post link may use either domain: dynt.news/article/<id> or
    # dyntland.com/article/<id>.
    match = re.search(r'(?:dynt\.news|dyntland\.com)/article/(\d+)', text)
    if match:
        return int(match.group(1))

    return None


# ============================
# FETCH REELS / MEDIA
# ============================

def fetch_page_reels(since, until):
    reels = []
    params = {
        "fields": "id,created_time,permalink_url,message,comments.limit(0).summary(true)"
    }
    params["since"] = parse_date(since)
    params["until"] = parse_date(until)

    while True:
        data = fb_get(f"{PAGE_ID}/feed", params)
        reels.extend(data.get("data", []))
        paging = data.get("paging", {})
        if "next" not in paging:
            break
        after = parse_qs(urlparse(paging["next"]).query)["after"][0]
        params["after"] = after

    return reels

def fetch_ig_media(since, until):
    media = []
    params = {
        "fields": "id,timestamp,permalink,media_type,caption,comments_count"
    }
    params["since"] = parse_date(since)
    params["until"] = parse_date(until)

    while True:
        data = fb_get(f"{IG_BUSINESS_ID}/media", params)
        media.extend(data.get("data", []))
        paging = data.get("paging", {})
        if "next" not in paging:
            break
        after = parse_qs(urlparse(paging["next"]).query)["after"][0]
        params["after"] = after

    return media


# ============================
# 🆕 REEL INSIGHTS
# ============================

def fetch_fb_reel_insights(reel_id):
    params = {
        "metric": "post_impressions",
    }

    try:
        data = fb_get(f"{reel_id}/insights", params)
        insights = {i["name"]: i["values"][0]["value"] for i in data.get("data", [])}

    except Exception as e:
        print(f"\n[INSIGHTS ERROR] reel_id={reel_id}")
        print(f"Error: {str(e)}\n")
        insights = {}

    return {
        "reel_views": 0,
        "reel_reach": insights.get("post_impressions", 0),
        "reel_shares": 0,
        "reel_like_count": 0,
        "reel_saves": 0
    }

def fetch_ig_reel_insights(media_id):
    params = {"metric": "impressions,reach,saved,shares"}
    try:
        data = fb_get(f"{media_id}/insights", params)
        insights = {i["name"]: i["values"][0]["value"] for i in data.get("data", [])}
    except Exception:
        insights = {}

    return {
        "reel_views": 0,
        "reel_reach": insights.get("reach", 0),
        "reel_shares": insights.get("shares", 0),
        "reel_like_count": 0,
        "reel_saves": insights.get("saved", 0)
    }


# ============================
# 🆕 COMMENTS + REPLIES
# ============================

def fetch_fb_comments_with_replies(reel_id, limit=None):
    all_comments = []
    params = {
        "limit": 100,
        "fields": "id,message,created_time,from,like_count,comment_count"
    }

    after = None

    while True:

        if after:
            params["after"] = after

        data = fb_get(f"{reel_id}/comments", params)

        comments = data.get("data", [])

        if not comments:
            break

        for comment in comments:
            all_comments.append((comment, None))

            if comment.get("comment_count", 0) > 0:
                replies = fb_get(
                    f"{comment['id']}/comments",
                    {"fields": params["fields"], "limit": 50}
                )
                for reply in replies.get("data", []):
                    all_comments.append((reply, comment["id"]))

            if limit and len(all_comments) >= limit:
                return all_comments[:limit]
            
        paging = data.get("paging", {})

        if "next" not in paging:
            break

        after = parse_qs(
            urlparse(paging["next"]).query
        ).get("after", [None])[0]

        if not after:
            break

    return all_comments[:limit]

def fetch_ig_comments_with_replies(media_id, limit=None):
    all_comments = []
    params = {
        "limit": 100,
        "fields": "id,text,timestamp,username,like_count,replies"
    }

    after = None
    while True:

        if after:
            params["after"] = after

        data = fb_get(f"{media_id}/comments", params)
        comments = data.get("data", [])
        if not comments:
            break

        for comment in comments:
            all_comments.append((comment, None))

            replies = comment.get("replies", {}).get("data", [])

            for reply in replies:
                all_comments.append((reply, comment["id"]))

            if limit and len(all_comments) >= limit:
                return all_comments[:limit]
            
        paging = data.get("paging", {})

        if "next" not in paging:
            break

        after = parse_qs(urlparse(paging["next"]).query).get("after", [None])[0]

        if not after:
            break

    return all_comments[:limit]


# ============================
# FINGERPRINT
# ============================

def create_reel_fingerprint(post, platform):
    ts = post["created_time"] if platform == "facebook" else post["timestamp"]
    ts = datetime.strptime(ts, "%Y-%m-%dT%H:%M:%S%z")
    hour = ts.replace(minute=0, second=0, microsecond=0)
    text = (post.get("message") or post.get("caption") or "")[:50]
    text_hash = hashlib.md5(text.encode("utf-8")).hexdigest()[:8]
    # return f"{hour.isoformat()}_{abs(hash(text)) % 10000}"
    return f"{hour.isoformat()}_{text_hash}"


# ============================
# PROCESS ALL
# ============================

def process_all_comments(fb_reels, ig_media):
    rows = []

    print("📝 Facebook comments + replies...")
    for reel in fb_reels:
        fp = create_reel_fingerprint(reel, "facebook")
        # insights = fetch_fb_reel_insights(reel["id"]) #facebook insights are missing and generating errors, so leaving as 0 for now
        insights = {
            "reel_views": 0,
            "reel_reach": 0,
            "reel_shares": 0,
            "reel_like_count": 0,
            "reel_saves": 0
        }
        comments = fetch_fb_comments_with_replies(reel["id"])

        for comment, parent_id in comments:
            comment_text = clean_text(
            comment.get("message", "") or comment.get("text", "")
            )
            if not is_valid_comment(comment_text):
                continue
            author = comment.get("from", {})
            article_id = extract_article_id(reel.get("message", ""))
            rows.append({
                "article_id": article_id,
                "raw_json": json.dumps(comment),
                "reel_fingerprint": fp,
                "platform": PLATFORM_FACEBOOK,
                "reel_id": reel["id"],
                "reel_title": clean_text(reel.get("message", "")[:200]),
                "reel_created_time": reel["created_time"],
                "reel_url": reel.get("permalink_url", ""),
                **insights,
                "reel_comments_count": reel.get("comments", {}).get("summary", {}).get("total_count", 0),
                "comment_id": comment["id"],
                "parent_comment_id": parent_id,
                "is_reply": parent_id is not None,
                # "comment_message": clean_text(comment.get("message", "")), #old version
                "comment_message": comment_text,
                "comment_created_time": comment["created_time"],
                "comment_author_id": author.get("id", ""),
                "comment_author_name": author.get("name", ""),
                "comment_like_count": comment.get("like_count", 0),
                "comment_reply_count": comment.get("comment_count", 0)
            })

    print("📝 Instagram comments + replies...")
    for media in ig_media:
        fp = create_reel_fingerprint(media, "instagram")
        insights = {
            "reel_views": 0,
            "reel_reach": 0,
            "reel_shares": 0,
            "reel_like_count": 0,
            "reel_saves": 0
        }
        # insights = fetch_ig_reel_insights(media["id"]) #instagram insights are missing and generating errors, so leaving as 0 for now
        comments = fetch_ig_comments_with_replies(media["id"])

        for comment, parent_id in comments:
            comment_text = clean_text(
            comment.get("message", "") or comment.get("text", "")
            )
            if not is_valid_comment(comment_text):
                continue
            article_id = extract_article_id(media.get("caption", ""))
            rows.append({
                "article_id": article_id,
                "raw_json": json.dumps(comment),
                "reel_fingerprint": fp,
                "platform": PLATFORM_INSTAGRAM,
                "reel_id": media["id"],
                "reel_title": clean_text(media.get("caption", "")[:200]),
                "reel_created_time": media["timestamp"],
                "reel_url": media.get("permalink", ""),
                **insights,
                "reel_comments_count": media.get("comments_count", 0),
                "comment_id": comment["id"],
                "parent_comment_id": parent_id,
                "is_reply": parent_id is not None,
                # "comment_message": clean_text(comment.get("text", "")), #old version
                "comment_message": comment_text,
                "comment_created_time": comment.get("timestamp", ""),
                "comment_author_id": comment.get("username", ""),
                "comment_author_name": comment.get("username", ""),
                "comment_like_count": comment.get("like_count", 0),
                "comment_reply_count": len(comment.get("replies", {}).get("data", []))
            })

    return rows


# ============================
# MAIN
# ============================

def get_date_range():
    parser = argparse.ArgumentParser(description="Download FB/IG comments")
    parser.add_argument("--since", default=None, help="Start date MM/DD/YYYY (default: yesterday)")
    parser.add_argument("--until", default=None, help="End date MM/DD/YYYY (default: today)")
    args = parser.parse_args()

    today = datetime.now()
    yesterday = today - timedelta(days=1)

    since = args.since or yesterday.strftime("%m/%d/%Y")
    until = args.until or today.strftime("%m/%d/%Y")

    return since, until


def main():
    check_env()

    FROM_DATE, TO_DATE = get_date_range()
    print(f"Fetching comments from {FROM_DATE} to {TO_DATE}")

    fb_reels = fetch_page_reels(FROM_DATE, TO_DATE)
    ig_media = fetch_ig_media(FROM_DATE, TO_DATE)

    data = process_all_comments(fb_reels, ig_media)

    def format_datetime_columns(df):
        datetime_cols = [
            "reel_created_time",
            "comment_created_time"
        ]

        for col in datetime_cols:
            if col in df.columns:
                df[col] = pd.to_datetime(df[col], errors="coerce")
                df[col] = df[col].dt.strftime("%Y-%m-%d %H:%M:%S")

        return df

    # ts = datetime.now().strftime("%Y%m%d_%H%M%S")
    # csv_file = f"unified_reels_comments_{ts}.csv"
    # parquet_file = csv_file.replace(".csv", ".parquet")

    df = pd.DataFrame(data)
    df = format_datetime_columns(df)

    # Convert NaN → None (important for MySQL)
    df = df.where(pd.notnull(df), None)

    df["article_id"] = pd.to_numeric(df["article_id"], errors="coerce")

    # df = df.head(10)
    
    insert_dataframe_ignore(df, "social_comments_raw")
    print(f"✅ Inserted {len(df)} rows into DB")

    platform_counts = df["platform"].value_counts(dropna=False)

    print("\nPlatform Breakdown:")
    print(platform_counts)




    # df.to_csv(csv_file, index=False, encoding="utf-8-sig")
    # df.to_parquet(parquet_file)

    # print(f"✅ Saved {len(df)} rows")
    # print(csv_file)
    # print(parquet_file)


if __name__ == "__main__":
    main()
