#!/usr/bin/env python3

import re
from datetime import datetime
from pathlib import Path

import pymysql


BASE_DIR = Path(__file__).resolve().parent.parent
DB_CONFIG = BASE_DIR / "config" / "database.php"


# ============================================================
# NEWS DECISION ENGINE V1.1 — DRY RUN
# ============================================================

MAX_STORIES = 100

# ------------------------------------------------------------
# AGE
# ------------------------------------------------------------

FRESH_MINUTES = 30
NORMAL_MAX_HOURS = 12
STALE_HOURS = 24


# ------------------------------------------------------------
# CONFIDENCE
# ------------------------------------------------------------

# Multi-source story confidence threshold.
CONFIDENCE_READY = 60.0

# Existing story engine gives normal single-source stories
# around this level.
SINGLE_SOURCE_BASE = 45.0


# ------------------------------------------------------------
# CONTENT
# ------------------------------------------------------------

MIN_TEXT_LENGTH = 12


# ------------------------------------------------------------
# V1.1 SINGLE-SOURCE SAFETY
# ------------------------------------------------------------

# Repetition inside ONE source is useful evidence that a story
# is developing, but MUST NOT by itself make the story READY.
#
# A single-source story needs unusually strong attention AND
# repeated reporting before V1.1 allows it through.

SINGLE_SOURCE_MIN_ITEMS = 2
SINGLE_SOURCE_MIN_VIEWS = 20000
SINGLE_SOURCE_MIN_IMPORTANCE = 55.0

# Very high attention emergency fallback.
#
# This still requires repeated items. One viral post by itself
# is NOT enough.
SINGLE_SOURCE_VIRAL_VIEWS = 50000
SINGLE_SOURCE_VIRAL_MIN_ITEMS = 2


# ============================================================
# DB
# ============================================================

def load_db_config():

    content = DB_CONFIG.read_text(
        encoding="utf-8",
        errors="ignore"
    )

    patterns = {
        "host":
            r"\$dbHost\s*=\s*['\"]([^'\"]+)['\"]",

        "database":
            r"\$dbName\s*=\s*['\"]([^'\"]+)['\"]",

        "user":
            r"\$dbUser\s*=\s*['\"]([^'\"]+)['\"]",

        "password":
            r"\$dbPass\s*=\s*['\"]([^'\"]*)['\"]",
    }

    result = {}

    for key, pattern in patterns.items():

        match = re.search(
            pattern,
            content
        )

        if not match:
            raise RuntimeError(
                f"Could not read DB config: {key}"
            )

        result[key] = match.group(1)

    result["charset"] = "utf8mb4"

    return result


# ============================================================
# HELPERS
# ============================================================

def clean_text(text):

    text = text or ""

    # Telegram usernames
    text = re.sub(
        r"@[A-Za-z0-9_]+",
        " ",
        text
    )

    # URLs
    text = re.sub(
        r"https?://\S+|www\.\S+",
        " ",
        text
    )

    # Persian normalization
    text = (
        text
        .replace("\u200c", " ")
        .replace("ي", "ی")
        .replace("ى", "ی")
        .replace("ك", "ک")
        .replace("ۀ", "ه")
        .replace("ة", "ه")
    )

    text = re.sub(
        r"\s+",
        " ",
        text
    ).strip()

    return text


def age_minutes(dt):

    if not dt:
        return 999999

    delta = datetime.now() - dt

    return max(
        0,
        delta.total_seconds() / 60
    )


# ============================================================
# STORY DECISION
# ============================================================

def decide(story):

    reasons = []

    source_count = int(
        story["real_source_count"] or 0
    )

    item_count = int(
        story["item_count"] or 0
    )

    confidence = float(
        story["confidence_score"] or 0
    )

    max_priority = float(
        story["max_priority"] or 1
    )

    total_views = int(
        story["total_views"] or 0
    )

    headline = clean_text(
        story["headline"]
    )

    primary_text = clean_text(
        story["primary_text"]
    )

    age = age_minutes(
        story["last_seen_at"]
    )


    # ========================================================
    # 1) INVALID / EMPTY
    # ========================================================

    if (
        len(headline) < MIN_TEXT_LENGTH
        and len(primary_text) < MIN_TEXT_LENGTH
    ):

        return {
            "decision": "IGNORE",
            "importance": 0,
            "confidence": confidence,
            "reasons": [
                "empty-content"
            ]
        }


    if headline.startswith(
        "News Story #"
    ):

        return {
            "decision": "IGNORE",
            "importance": 0,
            "confidence": confidence,
            "reasons": [
                "fallback-headline"
            ]
        }


    # ========================================================
    # 2) IMPORTANCE SCORE
    # ========================================================

    importance = 20.0


    # --------------------------------------------------------
    # INDEPENDENT SOURCES
    #
    # This is the strongest normal signal.
    # --------------------------------------------------------

    if source_count >= 2:

        importance += 20

        reasons.append(
            f"{source_count}-sources"
        )


    if source_count >= 3:

        importance += 10


    # --------------------------------------------------------
    # STORY ITEMS
    #
    # V1.1:
    # Multiple items still increase importance, but repetition
    # from one source is NOT treated as confirmation.
    # --------------------------------------------------------

    if item_count >= 2:

        importance += 5

        reasons.append(
            f"{item_count}-items"
        )


    if item_count >= 4:

        importance += 5


    # --------------------------------------------------------
    # SOURCE PRIORITY
    # --------------------------------------------------------

    if max_priority > 1:

        bonus = min(
            15,
            (max_priority - 1) * 10
        )

        importance += bonus

        reasons.append(
            f"priority-{max_priority:.2f}"
        )


    # --------------------------------------------------------
    # VIEWS
    #
    # Secondary signal only.
    #
    # Even 40k views on ONE item must not automatically
    # convert a story to READY.
    # --------------------------------------------------------

    if total_views >= 1000:

        importance += 5

        reasons.append(
            "views-1k"
        )


    if total_views >= 5000:

        importance += 5

        reasons.append(
            "views-5k"
        )


    if total_views >= 20000:

        importance += 5

        reasons.append(
            "views-20k"
        )


    if total_views >= 50000:

        importance += 5

        reasons.append(
            "views-50k"
        )


    # --------------------------------------------------------
    # FRESHNESS
    # --------------------------------------------------------

    if age <= FRESH_MINUTES:

        importance += 10

        reasons.append(
            "fresh"
        )

    elif age <= NORMAL_MAX_HOURS * 60:

        importance += 5

        reasons.append(
            "recent"
        )


    importance = min(
        100.0,
        importance
    )


    # ========================================================
    # 3) DECISION
    # ========================================================


    # --------------------------------------------------------
    # A) STALE
    # --------------------------------------------------------

    if age >= STALE_HOURS * 60:

        return {
            "decision": "IGNORE",
            "importance": importance,
            "confidence": confidence,
            "reasons":
                reasons + [
                    "stale"
                ]
        }


    # --------------------------------------------------------
    # B) MULTI-SOURCE CONFIRMATION
    #
    # Primary READY route.
    # --------------------------------------------------------

    if (
        source_count >= 2
        and confidence >= CONFIDENCE_READY
    ):

        return {
            "decision": "READY",
            "importance": importance,
            "confidence": confidence,
            "reasons":
                reasons + [
                    "multi-source-confirmed"
                ]
        }


    # --------------------------------------------------------
    # C) SINGLE-SOURCE REPEATED + STRONG ATTENTION
    #
    # Important V1.1 change:
    #
    # 2 posts from the same source are NOT enough anymore.
    #
    # It must also have strong audience attention.
    # --------------------------------------------------------

    if (
        source_count == 1
        and item_count >= SINGLE_SOURCE_MIN_ITEMS
        and confidence >= SINGLE_SOURCE_BASE
        and total_views >= SINGLE_SOURCE_MIN_VIEWS
        and importance >= SINGLE_SOURCE_MIN_IMPORTANCE
    ):

        return {
            "decision": "READY",
            "importance": importance,
            "confidence": confidence,
            "reasons":
                reasons + [
                    "strong-single-source-repeated"
                ]
        }


    # --------------------------------------------------------
    # D) EXTREME SINGLE-SOURCE ATTENTION
    #
    # Still requires multiple items.
    #
    # One viral Telegram post alone cannot become READY.
    # --------------------------------------------------------

    if (
        source_count == 1
        and item_count >= SINGLE_SOURCE_VIRAL_MIN_ITEMS
        and confidence >= SINGLE_SOURCE_BASE
        and total_views >= SINGLE_SOURCE_VIRAL_VIEWS
    ):

        return {
            "decision": "READY",
            "importance": importance,
            "confidence": confidence,
            "reasons":
                reasons + [
                    "viral-single-source-repeated"
                ]
        }


    # --------------------------------------------------------
    # E) SINGLE SOURCE — REPEATED BUT NOT CONFIRMED
    # --------------------------------------------------------

    if (
        source_count == 1
        and item_count >= 2
    ):

        return {
            "decision": "WAIT",
            "importance": importance,
            "confidence": confidence,
            "reasons":
                reasons + [
                    "single-source-repeat-not-confirmation"
                ]
        }


    # --------------------------------------------------------
    # F) SINGLE VIRAL ITEM
    # --------------------------------------------------------

    if (
        source_count == 1
        and total_views >= 20000
    ):

        return {
            "decision": "WAIT",
            "importance": importance,
            "confidence": confidence,
            "reasons":
                reasons + [
                    "viral-but-single-source"
                ]
        }


    # --------------------------------------------------------
    # G) DEFAULT
    # --------------------------------------------------------

    return {
        "decision": "WAIT",
        "importance": importance,
        "confidence": confidence,
        "reasons":
            reasons + [
                "needs-more-signal"
            ]
    }


# ============================================================
# LOAD STORIES
# ============================================================

def load_stories(db):

    sql = """
    SELECT
        st.id,
        st.headline,
        st.story_type,
        st.status,
        st.confidence_score,
        st.first_seen_at,
        st.last_seen_at,

        COUNT(DISTINCT si.id)
            AS item_count,

        COUNT(DISTINCT si.source_id)
            AS real_source_count,

        COALESCE(
            MAX(ns.priority),
            1
        ) AS max_priority,

        COALESCE(
            SUM(fi.source_views),
            0
        ) AS total_views,

        MAX(
            CASE
                WHEN si.is_primary = 1
                THEN fi.text_content
                ELSE NULL
            END
        ) AS primary_text

    FROM news_stories st

    LEFT JOIN news_story_items si
        ON si.story_id = st.id

    LEFT JOIN news_feed_items fi
        ON fi.id = si.feed_item_id

    LEFT JOIN news_sources ns
        ON ns.source_id = si.source_id

    WHERE st.status = 'collecting'

    GROUP BY
        st.id,
        st.headline,
        st.story_type,
        st.status,
        st.confidence_score,
        st.first_seen_at,
        st.last_seen_at

    ORDER BY
        st.last_seen_at DESC

    LIMIT %s
    """

    with db.cursor() as cursor:

        cursor.execute(
            sql,
            (
                MAX_STORIES,
            )
        )

        return cursor.fetchall()


# ============================================================
# DRY RUN
# ============================================================

def main():

    config = load_db_config()

    config["cursorclass"] = (
        pymysql.cursors.DictCursor
    )

    db = pymysql.connect(
        **config
    )

    try:

        stories = load_stories(
            db
        )

        print()
        print(
            "NEWS DECISION ENGINE V1.1 — DRY RUN"
        )
        print(
            "=" * 100
        )

        print(
            f"Stories evaluated: "
            f"{len(stories)}"
        )

        print(
            "=" * 100
        )


        counts = {
            "READY": 0,
            "WAIT": 0,
            "IGNORE": 0
        }


        ready_multi = 0
        ready_single = 0


        for story in stories:

            result = decide(
                story
            )

            decision = result[
                "decision"
            ]

            counts[
                decision
            ] += 1


            if decision == "READY":

                if int(
                    story[
                        "real_source_count"
                    ] or 0
                ) >= 2:

                    ready_multi += 1

                else:

                    ready_single += 1


            headline = clean_text(
                story["headline"]
            )[:110]


            print()

            print(
                f"STORY #{story['id']} "
                f"| {decision}"
            )


            print(
                f"Sources: "
                f"{story['real_source_count']} "
                f"| Items: "
                f"{story['item_count']} "
                f"| Views: "
                f"{story['total_views']}"
            )


            print(
                f"Confidence: "
                f"{result['confidence']:.2f} "
                f"| Importance: "
                f"{result['importance']:.2f}"
            )


            print(
                "Reasons: "
                + ", ".join(
                    result[
                        "reasons"
                    ]
                )
            )


            print(
                f"Headline: "
                f"{headline}"
            )


        print()
        print(
            "=" * 100
        )

        print(
            "SUMMARY"
        )

        print(
            "=" * 100
        )


        for key in (
            "READY",
            "WAIT",
            "IGNORE"
        ):

            print(
                f"{key}: "
                f"{counts[key]}"
            )


        print()
        print(
            "READY BREAKDOWN"
        )

        print(
            "-" * 100
        )

        print(
            f"Multi-source READY: "
            f"{ready_multi}"
        )

        print(
            f"Single-source READY: "
            f"{ready_single}"
        )


        print()
        print(
            "V1.1 SAFETY:"
        )

        print(
            "- Repetition from one source "
            "alone does NOT confirm a story."
        )

        print(
            "- Views alone do NOT confirm "
            "a story."
        )

        print(
            "- Multi-source confirmation "
            "remains the primary READY path."
        )

        print(
            "- Single-source READY requires "
            "repetition + unusually strong attention."
        )


        print()
        print(
            "DRY RUN ONLY — "
            "DATABASE NOT MODIFIED ✅"
        )


    finally:

        db.close()


if __name__ == "__main__":
    main()
