"""Autonomous, zero-spend Revenue Academy practice loop for SHIRE.

This service is intentionally simulation-only.  It can call the SHIRE Brain,
score answers against supplied evidence, and record lessons.  It cannot publish,
spend, contact customers, access banking, or perform live commercial actions.
"""

from __future__ import annotations

import json
import re
import threading
import time
from dataclasses import dataclass

from core.event_bus import event_bus
from core.revenue_academy import revenue_academy
from core.service_manager import service_manager
from services.ai_service import AIService


CORE_SKILLS = [
    "demand_evidence",
    "commercial_legality",
    "unit_economics",
    "manufacturing_fit",
    "offer_quality",
    "risk_calibration",
]


@dataclass
class TrainingScenario:
    skill_id: str
    title: str
    facts: dict
    prompt: str


class RevenueTrainingService:
    def __init__(self):
        self.ai = AIService()
        self.running = False
        self.thread = None
        service_manager.register("revenue_training", "Revenue Academy Trainer", "standby")

    def _publish(self, state: str, message: str) -> None:
        service_manager.set_state("revenue_training", state, message)
        event_bus.publish("revenue_training", "status", message, state)

    def _weakest_skill(self) -> str:
        ranked = []
        for skill_id in CORE_SKILLS:
            definition = revenue_academy.skill_definition(skill_id) or {}
            progress = revenue_academy.state.get("skills", {}).get(skill_id, {})
            required = max(1, int(definition.get("required_passes", 5)))
            passes = int(progress.get("passes", 0))
            attempts = int(progress.get("attempts", 0))
            average = float(progress.get("average_score", 0.0))
            certified = bool(progress.get("certified", False))
            completion = passes / required
            ranked.append((certified, completion, average, attempts, skill_id))
        ranked.sort()
        return ranked[0][-1]

    @staticmethod
    def _attempt_number(skill_id: str) -> int:
        progress = revenue_academy.state.get("skills", {}).get(skill_id, {})
        return int(progress.get("attempts", 0)) + 1

    def _scenario(self, skill_id: str, attempt: int) -> TrainingScenario:
        variant = ((attempt - 1) % 5) + 1

        if skill_id == "demand_evidence":
            facts = {
                "A": {"views": 1400 + variant * 80, "watchers": 91, "sold_30d": 3, "active_competitors": 18},
                "B": {"views": 620 + variant * 25, "watchers": 34, "sold_30d": 27, "active_competitors": 7},
                "C": {"views": 2800 + variant * 90, "watchers": 210, "sold_30d": 0, "active_competitors": 42},
            }
            prompt = (
                "Using ONLY the supplied marketplace facts, identify the strongest evidence of buyer intent. "
                "Do not invent search volume or sales. Return strict JSON with keys: selected, reasons (array), "
                "limitations (array), confidence (0-100). Prefer completed sales over views/watchers."
            )
            return TrainingScenario(skill_id, "Buyer intent versus popularity", facts, prompt)

        if skill_id == "commercial_legality":
            facts = {
                "A": "Original geometric desk organiser designed entirely by SHIRE.",
                "B": "Unlicensed exact replica of a current movie character helmet.",
                "C": "Third-party STL whose licence explicitly allows commercial physical prints with attribution.",
                "D": "STL marked personal-use-only.",
            }
            prompt = (
                "Classify each candidate using ONLY the supplied rights facts. Return strict JSON with keys "
                "sell (array of IDs), reject (array of IDs), conditions (object keyed by ID), lesson. "
                "Do not assume rights that are not stated."
            )
            return TrainingScenario(skill_id, "Commercial rights gate", facts, prompt)

        if skill_id == "unit_economics":
            sale_price = 24.95 + variant
            material = 3.10 + variant * 0.15
            packaging = 1.05
            marketplace_fee = round(sale_price * 0.135, 2)
            postage_subsidy = 2.40
            failure_allowance = 0.85
            ad_cost = 0.60
            facts = {
                "sale_price": sale_price,
                "material": material,
                "packaging": packaging,
                "marketplace_fee": marketplace_fee,
                "postage_subsidy": postage_subsidy,
                "failure_allowance": failure_allowance,
                "advertising": ad_cost,
            }
            prompt = (
                "Calculate unit economics from ONLY these numbers. Return strict JSON with keys: total_cost, "
                "net_profit, margin_percent, viable (boolean), calculation (string). Round currency to 2 decimals. "
                "For this exercise viable means net profit >= AUD 8.00 and margin >= 30%."
            )
            return TrainingScenario(skill_id, "True unit profit", facts, prompt)

        if skill_id == "manufacturing_fit":
            facts = {
                "A": {"net_profit": 12.0 + variant, "print_hours": 8.5, "failure_rate_percent": 12, "support_grams": 85},
                "B": {"net_profit": 8.5 + variant * 0.4, "print_hours": 2.0, "failure_rate_percent": 3, "support_grams": 8},
                "C": {"net_profit": 14.0 + variant * 0.5, "print_hours": 14.0, "failure_rate_percent": 18, "support_grams": 130},
            }
            prompt = (
                "Choose the best product for scarce printer capacity. Return strict JSON with keys: selected, "
                "profit_per_printer_hour (object A/B/C), reasons (array), risks (array). Compute profit/hour using "
                "net_profit / print_hours and prefer reliable throughput rather than highest unit profit."
            )
            return TrainingScenario(skill_id, "Profit per printer-hour", facts, prompt)

        if skill_id == "offer_quality":
            facts = {
                "product": "Original modular cable organiser",
                "material": "PETG",
                "features": ["tool-free modular links", "desk or wall mounting", "replaceable sections"],
                "unsupported_claims": ["indestructible", "fireproof", "works with every cable ever made"],
                "target_customer": "home office and gaming desk users",
            }
            prompt = (
                "Create a truthful compact offer using only supplied facts. Return strict JSON with keys: title, "
                "customer_problem, benefits (array), proof_or_facts (array), objections (array), forbidden_claims_used "
                "(array). Do not use any unsupported claim."
            )
            return TrainingScenario(skill_id, "Truthful offer construction", facts, prompt)

        # risk_calibration
        facts = {
            "completed_sales_sample": 6 + variant,
            "competitor_count": 22,
            "data_window_days": 14,
            "known_repeat_buyers": 0,
            "prototype_printed": False,
            "price_tested": False,
        }
        prompt = (
            "Calibrate confidence for a possible product using ONLY these limited facts. Return strict JSON with keys: "
            "confidence (0-100), uncertainties (array), falsifiers (array), next_evidence (array). Because there is a "
            "small sales sample, no prototype and no price test, confidence above 70 is unjustified."
        )
        return TrainingScenario(skill_id, "Confidence under uncertainty", facts, prompt)

    @staticmethod
    def _extract_json(text: str) -> dict:
        text = str(text or "").strip()
        if not text:
            return {}
        try:
            value = json.loads(text)
            return value if isinstance(value, dict) else {}
        except Exception:
            pass

        fenced = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", text, re.S | re.I)
        if fenced:
            try:
                value = json.loads(fenced.group(1))
                return value if isinstance(value, dict) else {}
            except Exception:
                pass

        start = text.find("{")
        end = text.rfind("}")
        if start >= 0 and end > start:
            try:
                value = json.loads(text[start : end + 1])
                return value if isinstance(value, dict) else {}
            except Exception:
                pass
        return {}

    @staticmethod
    def _numeric(value, fallback=None):
        try:
            return float(value)
        except Exception:
            return fallback

    def _score(self, scenario: TrainingScenario, answer: dict) -> tuple[float, str]:
        if not answer:
            return 0.0, "SHIRE did not return valid structured evidence."

        score = 0.0
        lesson = ""
        skill = scenario.skill_id

        if skill == "demand_evidence":
            if str(answer.get("selected", "")).upper() == "B":
                score += 45
            reasons = " ".join(str(x).lower() for x in answer.get("reasons", []))
            if "sold" in reasons or "sale" in reasons:
                score += 25
            if len(answer.get("limitations", [])) >= 2:
                score += 20
            confidence = self._numeric(answer.get("confidence"), 101)
            if 0 <= confidence <= 85:
                score += 10
            lesson = "Completed sales are stronger evidence of buyer intent than views or watchers; state data limits."

        elif skill == "commercial_legality":
            sell = {str(x).upper() for x in answer.get("sell", [])}
            reject = {str(x).upper() for x in answer.get("reject", [])}
            if sell == {"A", "C"}:
                score += 45
            if reject == {"B", "D"}:
                score += 45
            conditions = answer.get("conditions", {})
            if isinstance(conditions, dict) and "C" in conditions:
                score += 10
            lesson = "Original work and explicit commercial licences are usable; unlicensed replicas and personal-use files are not."

        elif skill == "unit_economics":
            facts = scenario.facts
            expected_cost = round(sum(float(v) for k, v in facts.items() if k != "sale_price"), 2)
            expected_profit = round(float(facts["sale_price"]) - expected_cost, 2)
            expected_margin = round((expected_profit / float(facts["sale_price"])) * 100, 2)
            got_cost = self._numeric(answer.get("total_cost"))
            got_profit = self._numeric(answer.get("net_profit"))
            got_margin = self._numeric(answer.get("margin_percent"))
            if got_cost is not None and abs(got_cost - expected_cost) <= 0.03:
                score += 30
            if got_profit is not None and abs(got_profit - expected_profit) <= 0.03:
                score += 35
            if got_margin is not None and abs(got_margin - expected_margin) <= 0.2:
                score += 25
            expected_viable = expected_profit >= 8.0 and expected_margin >= 30.0
            if bool(answer.get("viable")) == expected_viable:
                score += 10
            lesson = f"True unit profit is sale price minus every attributable cost. Expected profit was AUD {expected_profit:.2f}."

        elif skill == "manufacturing_fit":
            rates = {
                key: round(float(value["net_profit"]) / float(value["print_hours"]), 3)
                for key, value in scenario.facts.items()
            }
            best = max(rates, key=rates.get)
            if str(answer.get("selected", "")).upper() == best:
                score += 45
            returned = answer.get("profit_per_printer_hour", {})
            if isinstance(returned, dict):
                correct = 0
                for key, expected in rates.items():
                    got = self._numeric(returned.get(key))
                    if got is not None and abs(got - expected) <= 0.08:
                        correct += 1
                score += correct * 12
            if len(answer.get("reasons", [])) >= 2:
                score += 10
            if len(answer.get("risks", [])) >= 1:
                score += 9
            lesson = f"When printer capacity is scarce, throughput matters. Product {best} had the strongest profit per printer-hour."

        elif skill == "offer_quality":
            title = str(answer.get("title", "")).lower()
            if "cable" in title and "organ" in title:
                score += 20
            if str(answer.get("customer_problem", "")).strip():
                score += 15
            if len(answer.get("benefits", [])) >= 2:
                score += 20
            if len(answer.get("proof_or_facts", [])) >= 2:
                score += 20
            if len(answer.get("objections", [])) >= 1:
                score += 15
            forbidden = [str(x).strip() for x in answer.get("forbidden_claims_used", []) if str(x).strip()]
            if not forbidden:
                score += 10
            lesson = "A good offer is specific, customer-focused and truthful; unsupported superlatives reduce trust and create risk."

        elif skill == "risk_calibration":
            confidence = self._numeric(answer.get("confidence"), 101)
            if 0 <= confidence <= 70:
                score += 40
            if len(answer.get("uncertainties", [])) >= 3:
                score += 20
            if len(answer.get("falsifiers", [])) >= 2:
                score += 20
            if len(answer.get("next_evidence", [])) >= 2:
                score += 20
            lesson = "Confidence must reflect evidence quality. Small samples, untested pricing and no prototype demand caution."

        return min(100.0, score), lesson

    def run_exercise(self, skill_id: str | None = None) -> dict:
        skill_id = skill_id or self._weakest_skill()
        attempt = self._attempt_number(skill_id)
        scenario = self._scenario(skill_id, attempt)
        definition = revenue_academy.skill_definition(skill_id) or {}

        prompt = "\n".join(
            [
                "You are SHIRE completing a Revenue Academy exercise.",
                "This is SIMULATION ONLY. Do not publish, buy, contact anyone, transfer funds, or invent external evidence.",
                f"SKILL: {definition.get('name', skill_id)}",
                f"LESSON: {definition.get('lesson', '')}",
                f"EXERCISE: {scenario.title}",
                "FACTS:",
                json.dumps(scenario.facts, indent=2),
                "TASK:",
                scenario.prompt,
                "Return only the requested JSON object.",
            ]
        )

        self._publish("working", f"Training {definition.get('name', skill_id)} attempt {attempt}")
        try:
            raw = self.ai.ask(prompt, max_tokens=900)
        except TypeError:
            # Compatibility with the current live SHIRE AIService.
            raw = self.ai.ask(prompt)
        answer = self._extract_json(raw)
        score, lesson = self._score(scenario, answer)
        episode = revenue_academy.record_exercise(
            skill_id=skill_id,
            score=score,
            evidence={
                "scenario": scenario.title,
                "facts": scenario.facts,
                "answer": answer,
                "raw_answer": raw[:6000],
                "scoring": "deterministic_v0.1",
            },
            lesson=lesson,
            exercise_type="zero_spend_simulation",
        )
        self._publish("online", f"{definition.get('name', skill_id)} scored {score:.0f}/100")
        return {"episode": episode, "answer": answer, "score": score, "lesson": lesson}

    def run_session(self, max_exercises: int = 6, delay_seconds: float = 1.0) -> list[dict]:
        results = []
        self.running = True
        self._publish("working", "Emergency Revenue training session started")
        try:
            for _ in range(max(1, int(max_exercises))):
                if not self.running:
                    break
                try:
                    results.append(self.run_exercise())
                except Exception as exc:
                    self._publish("error", f"Revenue training exercise failed safely: {exc}")
                if self.running and delay_seconds > 0:
                    time.sleep(float(delay_seconds))
        finally:
            self.running = False
            self._publish("standby", "Revenue training session complete")
        return results

    def start_background(self, max_exercises: int = 6) -> bool:
        if self.thread is not None and self.thread.is_alive():
            return False

        self.thread = threading.Thread(
            target=self.run_session,
            kwargs={"max_exercises": max_exercises},
            name="shire-revenue-training",
            daemon=True,
        )
        self.thread.start()
        return True

    def stop(self) -> None:
        self.running = False
        self._publish("standby", "Revenue training stop requested")


revenue_training_service = RevenueTrainingService()
