"""Config, inbox state, and local training metadata for Superpitch."""

from __future__ import annotations

import json
import re
from datetime import datetime

from superpitch.app_paths import CONFIG_FILE, FEEDBACK_FILE, HISTORY_FILE


def default_config():
    return {
        "onboarding_complete": False,
        "onboarding_version": 0,
        "mode": "local",
        "signed_in": False,
        "preferred_stages": [],
        "thesis": "",
        "auto_retrain_enabled": True,
        "auto_retrain_threshold": 20,
        "last_trained_feedback_count": 0,
        "last_update_check_at": "",
        "latest_version_available": "",
        # cloud sync
        "session_token": "",
        "user_email": "",
        "user_slug": "",
        "last_synced_at": "",
        "contribute_enabled": False,
    }


def load_config():
    if not CONFIG_FILE.exists():
        return default_config()
    with open(CONFIG_FILE) as f:
        data = json.load(f)
    return {**default_config(), **data}


def save_config(config):
    CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
    with open(CONFIG_FILE, "w") as f:
        json.dump(config, f, indent=2)


def count_feedback_entries():
    if not FEEDBACK_FILE.exists():
        return 0
    with open(FEEDBACK_FILE) as f:
        return sum(1 for line in f if line.strip())


def feedback_since_last_train(config):
    return max(
        0, count_feedback_entries() - int(config.get("last_trained_feedback_count", 0))
    )


def should_auto_retrain(config):
    if not config.get("auto_retrain_enabled", True):
        return False
    return feedback_since_last_train(config) >= int(
        config.get("auto_retrain_threshold", 20)
    )


def mark_train_complete(config, feedback_count=None):
    updated = {**default_config(), **config}
    updated["last_trained_feedback_count"] = (
        count_feedback_entries() if feedback_count is None else int(feedback_count)
    )
    save_config(updated)
    return updated


def normalize_history_item(item):
    return {
        **item,
        "opened_at": item.get("opened_at"),
        "decision_state": item.get("decision_state"),
        "decided_at": item.get("decided_at"),
        "review_reasons": item.get("review_reasons") or [],
        "signals": item.get("signals") or {"positive": [], "negative": []},
        "diagnostics": item.get("diagnostics")
        or {"unusual_signals": [], "outlier_count": 0},
        "sub_scores": item.get("sub_scores") or {},
        "source_label": item.get("source_label", "manual"),
        "cloud_pitch_id": item.get("cloud_pitch_id"),  # None for local-only pitches
    }


def load_history(limit=None):
    if not HISTORY_FILE.exists():
        return []
    items = []
    with open(HISTORY_FILE) as f:
        for line in f:
            line = line.strip()
            if line:
                items.append(normalize_history_item(json.loads(line)))
    if limit is not None:
        return items[-limit:]
    return items


def save_history(items):
    HISTORY_FILE.parent.mkdir(parents=True, exist_ok=True)
    with open(HISTORY_FILE, "w") as f:
        for item in items:
            f.write(json.dumps(item) + "\n")


def update_history_item(index, item):
    items = load_history()
    if 0 <= index < len(items):
        items[index] = item
        save_history(items)


def save_history_entry(
    text, inp, result, source_label,
    cloud_pitch_id=None,
    ai_explanation=None,
    ai_questions=None,
):
    HISTORY_FILE.parent.mkdir(parents=True, exist_ok=True)
    record = {
        "timestamp": datetime.now().isoformat(),
        "source_label": source_label,
        "one_liner": (inp or {}).get("one_liner") or text.strip().splitlines()[0][:160],
        "text": text,
        "input": inp,
        "pred_score": round(result["pred_score"], 2),
        "final_action": result["final_action"],
        "confidence": result["confidence"]["level"],
        "review_reasons": result["review_reasons"],
        "sub_scores": {k: round(v, 2) for k, v in result["sub"].items()},
        "signals": result["signals"],
        "diagnostics": result["diagnostics"],
        "is_moonshot": result["is_moonshot"],
        "specificity": result["specificity"],
        "opened_at": None,
        "decision_state": None,
        "decided_at": None,
        "cloud_pitch_id": cloud_pitch_id,
        "ai_explanation": ai_explanation,
        "ai_questions": ai_questions or [],
    }
    with open(HISTORY_FILE, "a") as f:
        f.write(json.dumps(record) + "\n")
    return record


def history_bucket(item):
    if item.get("decision_state"):
        return "decided"
    if item.get("opened_at"):
        return "seen"
    return "new"


def inbox_counts(items):
    counts = {"new": 0, "seen": 0, "decided": 0}
    for item in items:
        counts[history_bucket(item)] += 1
    return counts


def inbox_sort_key(item):
    bucket_order = {"new": 0, "seen": 1, "decided": 2}
    action_order = {"REVIEW": 0, "APPROVE": 1, "REJECT": 2}
    return (
        bucket_order[history_bucket(item)],
        action_order.get(item.get("final_action"), 9),
        -float(item.get("pred_score", 0)),
        item.get("timestamp", ""),
    )


def ordered_history(items):
    indexed = list(enumerate(items))
    indexed.sort(key=lambda pair: inbox_sort_key(pair[1]))
    return indexed


def mark_pitch_opened(real_index, item):
    if item.get("opened_at") is None:
        item["opened_at"] = datetime.now().isoformat()
        update_history_item(real_index, item)
    return item


def profile_status_line(config):
    if config.get("signed_in") and config.get("user_email"):
        slug = config.get("user_slug", "")
        return f"signed in  ·  {config['user_email']}" + (f"  ·  superpitch.dev/{slug}" if slug else "")
    return "local only  ·  /signin to sync"


# ── Stage / thesis matching ──────────────────────────────────────────────────

STAGE_OPTIONS = [
    ("pre_seed", "Pre-seed"),
    ("seed", "Seed"),
    ("series_a", "Series A"),
    ("series_b", "Series B"),
    ("series_c_plus", "Series C+"),
]
STAGE_MAP = dict(STAGE_OPTIONS)

_STAGE_PATTERNS = {
    "pre_seed": [r"pre[\s-]?seed"],
    "seed": [r"\bseed\b"],
    "series_a": [r"series\s*a", r"\ba\s*round\b"],
    "series_b": [r"series\s*b", r"\bb\s*round\b"],
    "series_c_plus": [r"series\s*c", r"series\s*d", r"series\s*e", r"late[\s-]?stage", r"growth\s+stage"],
}
_COMPILED_STAGE_PATTERNS = {
    k: [re.compile(p) for p in patterns]
    for k, patterns in _STAGE_PATTERNS.items()
}

_THESIS_STOPWORDS = {
    "about", "after", "almost", "also", "among", "because", "been", "both",
    "company", "companies", "could", "does", "every", "focus", "focused",
    "for", "from", "have", "into", "just", "like", "market", "maybe", "more",
    "mostly", "much", "only", "over", "really", "seed", "series", "stage",
    "startup", "startups", "that", "their", "there", "these", "they", "this",
    "those", "very", "what", "when", "where", "with", "would", "your",
}


def format_stage_list(stage_keys):
    if not stage_keys:
        return "All stages"
    return ", ".join(STAGE_MAP[k] for k in stage_keys if k in STAGE_MAP) or "All stages"


def parse_stage_selection(raw: str):
    if not raw.strip():
        return [k for k, _ in STAGE_OPTIONS]
    aliases = {}
    for idx, (key, label) in enumerate(STAGE_OPTIONS, 1):
        aliases[str(idx)] = key
        aliases[key] = key
        aliases[label.lower()] = key
        aliases[label.lower().replace("+", "_plus").replace(" ", "_")] = key
    selected, seen = [], set()
    for token in re.split(r"[\s,]+", raw.strip().lower()):
        if token in {"all", "*"}:
            return [k for k, _ in STAGE_OPTIONS]
        key = aliases.get(token)
        if key and key not in seen:
            seen.add(key)
            selected.append(key)
    return selected


def _item_text_blob(item):
    parts = [item.get("text", ""), item.get("one_liner", ""), item.get("source_label", "")]
    inp = item.get("input") or {}
    for field in ("stage", "raising", "traction", "team", "market", "business_model", "competition", "summary"):
        if inp.get(field):
            parts.append(str(inp[field]))
    return " ".join(p for p in parts if p).lower()


def _detect_stage(text):
    for key, patterns in _COMPILED_STAGE_PATTERNS.items():
        for pat in patterns:
            if pat.search(text):
                return key
    return None


def _extract_thesis_terms(thesis: str):
    unique, seen = [], set()
    for word in re.findall(r"[a-z0-9][a-z0-9-]+", thesis.lower()):
        if len(word) >= 4 and word not in _THESIS_STOPWORDS and word not in seen:
            seen.add(word)
            unique.append(word)
        if len(unique) == 8:
            break
    return unique


def preference_fit(item, config, *, thesis_terms=None, text=None):
    """Return fit dict: {score, label, stage_match, detected_stage, thesis_hits}."""
    preferred = config.get("preferred_stages") or []
    text = _item_text_blob(item) if text is None else text
    detected = _detect_stage(text)
    stage_match = bool(detected and detected in preferred)
    terms = _extract_thesis_terms(config.get("thesis", "")) if thesis_terms is None else thesis_terms
    hits = [t for t in terms if t in text]
    score = (2 if stage_match else 0) + min(2, len(hits))
    if stage_match and hits:
        label = "STRONG"
    elif stage_match or hits:
        label = "FIT"
    else:
        label = ""
    return {"score": score, "label": label, "stage_match": stage_match,
            "detected_stage": detected, "thesis_hits": hits[:2]}


def build_ranked_items(items, config):
    """Return list of (real_index, item, fit) sorted by preference + score."""
    terms = _extract_thesis_terms(config.get("thesis", ""))
    ranked = [
        (i, item, preference_fit(item, config, thesis_terms=terms, text=_item_text_blob(item)))
        for i, item in enumerate(items)
    ]
    ranked.sort(key=lambda e: (
        0 if history_bucket(e[1]) != "decided" else 1,
        {"REVIEW": 0, "APPROVE": 1, "REJECT": 2}.get(e[1].get("final_action"), 9),
        -float(e[1].get("pred_score", 0)),
        -e[2]["score"],
        e[1].get("timestamp", ""),
    ))
    return ranked
