"""
Agent Verification Script — Tests all 3 agent subsystems.

Tests:
  1. Vision Model (CPU image classification + fashion mapping)
  2. Feedback Agent (learning loop import + rule generation prompt)
  3. Report Agent Scheduler (Hermes cron integration)

Run: python verify_agents.py
All tests must PASS for deployment.
"""

import asyncio
import io
import os
import sys
import traceback

# Setup paths
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
sys.path.append(os.path.join(os.path.dirname(__file__), "backend"))

PASS = "✅ PASS"
FAIL = "❌ FAIL"
SKIP = "⚠️ SKIP"
results = []


def record(name: str, status: str, detail: str = ""):
    results.append((name, status, detail))
    print(f"  {status} {name}" + (f" — {detail}" if detail else ""))


# ─── Test 1: Vision Model ───────────────────────────────────────────

async def test_vision_model():
    print("\n=== Test 1: Vision Model ===")

    try:
        from agent.image_search_agent.vision_model import vision_model, CATEGORY_RULES, COLOR_RULES
        record("import vision_model", PASS)
    except Exception as e:
        record("import vision_model", FAIL, str(e))
        return

    # Check rules are populated
    if len(CATEGORY_RULES) >= 20:
        record("category_rules count", PASS, f"{len(CATEGORY_RULES)} rules")
    else:
        record("category_rules count", FAIL, f"Only {len(CATEGORY_RULES)} rules, expected ≥20")

    if len(COLOR_RULES) >= 15:
        record("color_rules count", PASS, f"{len(COLOR_RULES)} rules")
    else:
        record("color_rules count", FAIL, f"Only {len(COLOR_RULES)} rules, expected ≥15")

    # Test rule matching logic (no model needed)
    test_text = "jersey, red sports shirt"
    cat = vision_model._match_first(test_text, CATEGORY_RULES, "unknown")
    if cat == "áo thun":
        record("category matching", PASS, f"'{test_text}' → '{cat}'")
    else:
        record("category matching", FAIL, f"Expected 'áo thun', got '{cat}'")

    test_text2 = "blue denim jacket"
    from agent.image_search_agent.vision_model import LocalVisionModel
    all_cats = LocalVisionModel._match_all(test_text2, CATEGORY_RULES)
    if "quần jeans" in all_cats or "áo khoác" in all_cats:
        record("multi-category matching", PASS, f"'{test_text2}' → {all_cats}")
    else:
        record("multi-category matching", FAIL, f"Expected jeans/jacket, got {all_cats}")

    # Test actual model inference (needs torch + transformers)
    try:
        from PIL import Image
        img = Image.new("RGB", (224, 224), color="black")
        test_path = "_test_vision_verify.jpg"
        img.save(test_path)

        result = vision_model.analyze_image(test_path)
        os.remove(test_path)

        if result.get("success") or result.get("features"):
            record("model inference", PASS, f"confidence={result.get('confidence', 'N/A')}")
        elif "error" in result and "sẵn sàng" in result["error"]:
            record("model inference", SKIP, "Model not downloaded yet (expected in CI)")
        else:
            record("model inference", FAIL, result.get("error", "Unknown error"))
    except ImportError:
        record("model inference", SKIP, "torch/transformers not installed")
    except Exception as e:
        record("model inference", FAIL, str(e))


# ─── Test 2: Visual Search Agent ────────────────────────────────────

async def test_visual_search_agent():
    print("\n=== Test 2: Visual Search Agent ===")

    try:
        from agent.image_search_agent.agent import handle_visual_search, _resolve_image_input
        record("import agent", PASS)
    except Exception as e:
        record("import agent", FAIL, str(e))
        return

    # Test _resolve_image_input with a fake path
    resolved = await _resolve_image_input("nonexistent_file.jpg")
    if resolved is None:
        record("resolve bad input", PASS, "Returns None for invalid input")
    else:
        record("resolve bad input", FAIL, f"Should return None, got {resolved}")

    # Test with a real temp file
    try:
        from PIL import Image
        img = Image.new("RGB", (224, 224), color=(0, 0, 128))  # Navy blue
        test_path = "_test_agent_verify.jpg"
        img.save(test_path)

        resolved = await _resolve_image_input(test_path)
        if resolved == test_path:
            record("resolve local file", PASS)
        else:
            record("resolve local file", FAIL, f"Expected {test_path}, got {resolved}")

        os.remove(test_path)
    except ImportError:
        record("resolve local file", SKIP, "PIL not installed")


# ─── Test 3: Image Search Graph ─────────────────────────────────────

async def test_image_search_graph():
    print("\n=== Test 3: Image Search Graph ===")

    try:
        from agent.image_search_agent.image_search_graph import ImageSearchGraph, get_image_search_agent
        record("import image_search_graph", PASS)
    except Exception as e:
        record("import image_search_graph", FAIL, str(e))
        return

    # Test lazy init
    try:
        agent = get_image_search_agent()
        if agent is not None:
            record("get_image_search_agent()", PASS)
        else:
            record("get_image_search_agent()", FAIL, "Returns None")
    except Exception as e:
        record("get_image_search_agent()", FAIL, str(e))

    # Test graph compilation
    try:
        graph = agent.build()
        if graph is not None:
            record("graph.build()", PASS)
        else:
            record("graph.build()", FAIL, "Returns None")
    except Exception as e:
        record("graph.build()", FAIL, str(e))


# ─── Test 4: Feedback Agent Learning Loop ───────────────────────────

async def test_feedback_agent():
    print("\n=== Test 4: Feedback Agent Learning Loop ===")

    try:
        from agent.feedback_agent.learning_loop import (
            generate_optimizer_prompt,
            persist_new_rule,
            run_optimization_logic,
        )
        record("import learning_loop", PASS)
    except Exception as e:
        record("import learning_loop", FAIL, str(e))
        return

    # Test prompt generation (no LLM needed)
    feedbacks = [
        {
            "user_query": "Tìm áo sơ mi size lớn",
            "ai_response": "Bạn xem mẫu này freesize nhé",
            "feedback_text": "Mình mập 80kg freesize sao vừa",
        }
    ]
    current_rules = "1. Luôn tư vấn thân thiện."

    prompt = generate_optimizer_prompt(feedbacks, current_rules)
    if "EXPLICIT INSTRUCTIONS" in prompt and "BAD FEEDBACK" in prompt:
        record("generate_optimizer_prompt", PASS, f"Prompt length: {len(prompt)} chars")
    else:
        record("generate_optimizer_prompt", FAIL, "Missing expected sections")


# ─── Test 5: Report Agent Scheduler ─────────────────────────────────

async def test_report_scheduler():
    print("\n=== Test 5: Report Agent Scheduler ===")

    try:
        from agent.report_agent.scheduler import (
            start_scheduler,
            automated_daily_sales_report,
            _hermes_available,
        )
        record("import scheduler", PASS)
    except Exception as e:
        record("import scheduler", FAIL, str(e))
        return

    # Check Hermes availability
    if _hermes_available:
        record("hermes cron available", PASS)
    else:
        record("hermes cron available", SKIP, "Hermes cron not on sys.path (fallback enabled)")

    # Test that start_scheduler doesn't crash
    try:
        # Don't actually start (it would register real cron jobs)
        # Just verify the function exists and is callable
        if callable(start_scheduler):
            record("start_scheduler callable", PASS)
        else:
            record("start_scheduler callable", FAIL)
    except Exception as e:
        record("start_scheduler callable", FAIL, str(e))


# ─── Test 6: Report Agent Graph ─────────────────────────────────────

async def test_report_graph():
    print("\n=== Test 6: Report Agent Graph ===")

    try:
        from agent.report_agent.main_graph import build_report_graph, run_report_agent
        record("import main_graph", PASS)
    except Exception as e:
        record("import main_graph", FAIL, str(e))
        return

    try:
        graph = build_report_graph()
        if graph is not None:
            record("build_report_graph()", PASS)
        else:
            record("build_report_graph()", FAIL, "Returns None")
    except Exception as e:
        record("build_report_graph()", FAIL, str(e))


# ─── Test 7: Import Chain (Server) ──────────────────────────────────

async def test_import_chain():
    print("\n=== Test 7: Import Chain ===")

    modules_to_test = [
        "agent.image_search_agent.vision_model",
        "agent.image_search_agent.agent",
        "agent.image_search_agent.image_search_graph",
        "agent.feedback_agent.learning_loop",
        "agent.report_agent.scheduler",
        "agent.report_agent.main_graph",
        "agent.report_agent.follow_up_graph",
        "agent.report_agent.inline_graph",
        "agent.report_agent.core",
        "agent.report_agent.report_queue",
        # Hermes deep integration modules
        "agent.report_agent.hermes_bridge",
        "agent.report_agent.insights_adapter",
        "agent.report_agent.error_recovery",
        "agent.report_agent.context_manager",
        "agent.report_agent.session_tracker",
        "agent.report_agent.autonomous_loop",
    ]

    for mod in modules_to_test:
        try:
            __import__(mod)
            record(f"import {mod.split('.')[-1]}", PASS)
        except Exception as e:
            record(f"import {mod.split('.')[-1]}", FAIL, str(e)[:80])


# ─── Test 8: Hermes Bridge ──────────────────────────────────────────

async def test_hermes_bridge():
    print("\n=== Test 8: Hermes Bridge ===")

    try:
        from agent.report_agent.hermes_bridge import (
            FailoverReason, ClassifiedError, classify_error,
            ReportSessionDB, format_duration,
        )
        record("import hermes_bridge", PASS)
    except Exception as e:
        record("import hermes_bridge", FAIL, str(e))
        return

    # Test FailoverReason enum
    reasons = list(FailoverReason)
    if len(reasons) >= 10:
        record("FailoverReason enum", PASS, f"{len(reasons)} reasons")
    else:
        record("FailoverReason enum", FAIL, f"Only {len(reasons)} reasons")

    # Test classify_error
    try:
        err = classify_error(ValueError("context length exceeded"))
        if err.reason == FailoverReason.context_overflow and err.should_compress:
            record("classify_error context_overflow", PASS)
        else:
            record("classify_error context_overflow", FAIL, f"Got {err.reason}")
    except Exception as e:
        record("classify_error context_overflow", FAIL, str(e))

    # Test ReportSessionDB
    try:
        import tempfile
        from pathlib import Path
        db_path = Path(tempfile.mktemp(suffix=".db"))
        db = ReportSessionDB(db_path=db_path)
        sid = db.create_session("test-001", "Test question")
        assert sid == "test-001"
        db.end_session("test-001", status="done", cycles_count=3)
        session = db.get_session("test-001")
        assert session["status"] == "done"
        db.close()
        db_path.unlink(missing_ok=True)
        record("ReportSessionDB lifecycle", PASS)
    except Exception as e:
        record("ReportSessionDB lifecycle", FAIL, str(e))

    # Test format_duration
    if format_duration(90) == "1m30s" and format_duration(3600) == "1h":
        record("format_duration", PASS)
    else:
        record("format_duration", FAIL, f"Got {format_duration(90)}, {format_duration(3600)}")


# ─── Test 9: Insights Adapter ───────────────────────────────────────

async def test_insights_adapter():
    print("\n=== Test 9: Insights Adapter ===")

    try:
        from agent.report_agent.insights_adapter import ReportInsightsEngine
        record("import insights_adapter", PASS)
    except Exception as e:
        record("import insights_adapter", FAIL, str(e))
        return

    try:
        import tempfile
        from pathlib import Path
        from agent.report_agent.hermes_bridge import ReportSessionDB

        db_path = Path(tempfile.mktemp(suffix=".db"))
        db = ReportSessionDB(db_path=db_path)

        # Seed test data
        import time
        for i in range(5):
            db.create_session(f"test-{i}", f"Question {i}")
            db.end_session(
                f"test-{i}", status="done", cycles_count=2,
                input_tokens=500, output_tokens=200,
                generation_time_ms=5000 + i * 1000,
            )

        engine = ReportInsightsEngine(db=db)
        report = engine.generate(days=1)

        if not report.get("empty"):
            overview = report.get("overview", {})
            if overview.get("total_sessions") == 5:
                record("insights_adapter generate()", PASS, f"5 sessions analyzed")
            else:
                record("insights_adapter generate()", FAIL, f"Got {overview.get('total_sessions')}")

            summary = engine.format_summary(report)
            if "Report Agent Insights" in summary:
                record("insights_adapter format_summary()", PASS, f"{len(summary)} chars")
            else:
                record("insights_adapter format_summary()", FAIL, "Missing header")
        else:
            record("insights_adapter generate()", FAIL, "Report is empty")

        db.close()
        db_path.unlink(missing_ok=True)
    except Exception as e:
        record("insights_adapter generate()", FAIL, str(e))


# ─── Test 10: Error Recovery ────────────────────────────────────────

async def test_error_recovery():
    print("\n=== Test 10: Error Recovery ===")

    try:
        from agent.report_agent.error_recovery import (
            with_recovery, RetryPolicy, RecoveryResult, classify_and_log,
        )
        record("import error_recovery", PASS)
    except Exception as e:
        record("import error_recovery", FAIL, str(e))
        return

    # Test successful call
    async def ok_fn():
        return "success"

    result = await with_recovery(ok_fn)
    if result.success and result.value == "success" and result.attempts == 1:
        record("with_recovery success", PASS)
    else:
        record("with_recovery success", FAIL, f"Got {result}")

    # Test retryable error
    call_count = 0

    async def fail_then_succeed():
        nonlocal call_count
        call_count += 1
        if call_count < 3:
            raise ValueError("rate limit exceeded")
        return "recovered"

    result = await with_recovery(
        fail_then_succeed,
        policy=RetryPolicy(max_retries=5, base_backoff_seconds=0.01),
    )
    if result.success and result.value == "recovered" and result.attempts == 3:
        record("with_recovery retry", PASS, f"Recovered after {result.attempts} attempts")
    else:
        record("with_recovery retry", FAIL, f"attempts={result.attempts}, success={result.success}")


# ─── Test 11: Context Manager ───────────────────────────────────────

async def test_context_manager():
    print("\n=== Test 11: Context Manager ===")

    try:
        from agent.report_agent.context_manager import ReportContextManager
        record("import context_manager", PASS)
    except Exception as e:
        record("import context_manager", FAIL, str(e))
        return

    mgr = ReportContextManager(max_tokens=100, tool_budget=50)

    # Test compression of large results
    results = [
        {"tool": "sql_query_0", "result": "x" * 500, "priority": 2},
        {"tool": "sql_query_1", "result": "y" * 500, "priority": 1},
    ]
    compressed = mgr.compress_tool_results(results)
    budget = mgr.get_budget_info()
    if budget.compressions_applied > 0:
        record("context_manager compression", PASS, f"{budget.compressions_applied} compressions")
    else:
        record("context_manager compression", FAIL, "No compressions applied")

    # Test SQL result compression
    mgr2 = ReportContextManager(tool_budget=20)
    big_sql = "\n".join([f"col1\tcol2\tcol3"] + [f"row{i}\tval{i}\tdata{i}" for i in range(200)])
    compressed_sql = mgr2.compress_sql_result(big_sql, max_rows=10)
    if "ẩn" in compressed_sql or len(compressed_sql) < len(big_sql):
        record("context_manager SQL compress", PASS)
    else:
        record("context_manager SQL compress", FAIL, "SQL not compressed")


# ─── Test 12: Session Tracker ───────────────────────────────────────

async def test_session_tracker():
    print("\n=== Test 12: Session Tracker ===")

    try:
        from agent.report_agent.session_tracker import ReportSessionTracker
        record("import session_tracker", PASS)
    except Exception as e:
        record("import session_tracker", FAIL, str(e))
        return

    try:
        import tempfile
        from pathlib import Path
        from agent.report_agent.hermes_bridge import ReportSessionDB

        db_path = Path(tempfile.mktemp(suffix=".db"))
        db = ReportSessionDB(db_path=db_path)
        tracker = ReportSessionTracker(db=db)

        # Full lifecycle
        sid = tracker.start("Test report question", model="codex")
        tracker.log_tool_call(sid, "sql_query", {"query": "SELECT 1"})
        tracker.log_llm_call(sid, input_tokens=100, output_tokens=50)
        tracker.log_error(sid, "rate_limit", "429 Too Many Requests")
        tracker.log_compression(sid)
        summary = tracker.finish(sid, report_id=1, html_length=5000)

        if (
            summary["status"] == "done"
            and summary["errors"] == 1
            and summary["compressions"] == 1
            and "sql_query" in summary["tools_used"]
        ):
            record("session_tracker lifecycle", PASS, f"{summary['generation_time_ms']}ms")
        else:
            record("session_tracker lifecycle", FAIL, str(summary))

        db.close()
        db_path.unlink(missing_ok=True)
    except Exception as e:
        record("session_tracker lifecycle", FAIL, str(e))


# ─── Test 13: Autonomous Loop ──────────────────────────────────────

async def test_autonomous_loop():
    print("\n=== Test 13: Autonomous Loop ===")

    try:
        from agent.report_agent.autonomous_loop import ReportCurator
        record("import autonomous_loop", PASS)
    except Exception as e:
        record("import autonomous_loop", FAIL, str(e))
        return

    curator = ReportCurator(idle_threshold=1)

    # Test activity recording
    curator.record_activity()
    idle = curator.seconds_idle()
    if idle < 2:
        record("curator activity tracking", PASS, f"idle={idle:.1f}s")
    else:
        record("curator activity tracking", FAIL, f"idle={idle:.1f}s")

    # Test status API
    status = curator.get_status()
    if "idle_seconds" in status and "total_runs" in status:
        record("curator get_status()", PASS)
    else:
        record("curator get_status()", FAIL, str(status))

    # Test cron suggestions (may return empty if no data)
    suggestions = curator.suggest_cron_jobs()
    record("curator suggest_cron_jobs()", PASS, f"{len(suggestions)} suggestions")


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

async def main():
    print("=" * 60)
    print("🔍 Agent Verification Suite — Epic 23 (Hermes Deep Integration)")
    print("=" * 60)

    await test_vision_model()
    await test_visual_search_agent()
    await test_image_search_graph()
    await test_feedback_agent()
    await test_report_scheduler()
    await test_report_graph()
    await test_import_chain()

    # New Hermes integration tests
    await test_hermes_bridge()
    await test_insights_adapter()
    await test_error_recovery()
    await test_context_manager()
    await test_session_tracker()
    await test_autonomous_loop()

    # Summary
    print("\n" + "=" * 60)
    print("📊 RESULTS SUMMARY")
    print("=" * 60)

    pass_count = sum(1 for _, s, _ in results if s == PASS)
    fail_count = sum(1 for _, s, _ in results if s == FAIL)
    skip_count = sum(1 for _, s, _ in results if s == SKIP)
    total = len(results)

    print(f"  Total: {total} | {PASS}: {pass_count} | {FAIL}: {fail_count} | {SKIP}: {skip_count}")

    if fail_count > 0:
        print(f"\n  ❌ {fail_count} test(s) FAILED:")
        for name, status, detail in results:
            if status == FAIL:
                print(f"    - {name}: {detail}")
        print("\n  🚫 VERIFICATION FAILED")
        sys.exit(1)
    else:
        print(f"\n  🎉 ALL {pass_count} TESTS PASSED ({skip_count} skipped)")
        sys.exit(0)


if __name__ == "__main__":
    asyncio.run(main())
