import argparse
import json
from pathlib import Path
import sys
from . import DISPLAY_VERSION
from .catalog import Catalog, read_json
from .engine import Executor
from .paths import user_data


def main(argv=None):
    if sys.stdout is not None:
        sys.stdout.reconfigure(encoding="utf8")
    parser = argparse.ArgumentParser(description="마숙자 27호 " + DISPLAY_VERSION + " · 아르바이트 실행 준비")
    parser.add_argument("--version", action="version", version=DISPLAY_VERSION)
    sub = parser.add_subparsers(dest="command")
    run = sub.add_parser("run", help="Run an explicitly synthetic scenario")
    run.add_argument("--scenario", default="bag-resume")
    run.add_argument("--journal", type=Path)
    run.add_argument("--resume", action="store_true")
    sub.add_parser("check", help="Check local resource integrity and catalog bindings")
    search = sub.add_parser("search")
    search.add_argument("query", nargs="?", default="")
    impact = sub.add_parser("impact")
    impact.add_argument("source")
    impact.add_argument("--candidate", type=Path)
    sub.add_parser("coverage")
    sub.add_parser("arbeit-status", help="Actual integration coverage and exact live-test blockers")
    sub.add_parser("arbeit-coverage", help="Numeric code coverage separate from live execution readiness")
    gaps = sub.add_parser("arbeit-gaps", help="Read the requirement-based objective/gap matrix; no game access")
    gaps.add_argument("--quest-id", type=int)
    arbeit_impact = sub.add_parser("arbeit-impact", help="Read-only source replacement impact on operations, quests and requirements")
    arbeit_impact.add_argument("paths", nargs="+")
    prepare = sub.add_parser("arbeit-prepare", help="Create a durable live preparation journal; incomplete preflight cannot send input")
    prepare.add_argument("--storage", type=Path)
    prepare.add_argument("--quest-id", type=int, required=True)
    continuous = sub.add_parser('arbeit-run', help='Continue the real graph until attention/stop; game input stays OFF')
    continuous.add_argument('--storage', type=Path)
    continuous.add_argument('--quest-id', type=int, required=True)
    graph = sub.add_parser("arbeit-graph", help="Compile one source-selected quest using the common compiler")
    graph.add_argument("quest_id", type=int)
    sub.add_parser("arbeit-audit", help="Exhaustive definition/objective/operator binding audit")
    sub.add_parser("engine-check", help="Import all actual adapters and check the complete graph/resource set without game access")
    sub.add_parser("ocr-smoke", help="Read a generated local caption with bundled OCR; no game capture")
    probe = sub.add_parser("arbeit-probe", help="One-shot official read-only query; never sends game actions")
    probe.add_argument("--cli", type=Path, default=Path(r"C:\Nexon\MabinogiMobile\MabinogiMobile_CLI.exe"))
    probe.add_argument("--evidence-dir", type=Path)
    phase = sub.add_parser("arbeit-plan", help="Exercise source-bound planners with no current frame (reports missing evidence)")
    phase.add_argument("phase", choices=("travel_to_npc", "accept", "objective", "return_to_npc", "report", "deliver", "reward"))
    sub.add_parser("gui")
    smoke = sub.add_parser("ui-smoke", help="Construct and exercise local Tk UI, then close only this window")
    args = parser.parse_args(argv)
    catalog = Catalog()
    if args.command == "ocr-smoke":
        from PIL import Image, ImageDraw, ImageFont
        from .arbeit_targets import OfflineOCR
        fixture = Image.new("RGB", (640, 240), "white")
        ImageDraw.Draw(fixture).text((120, 70), "수락", fill="black", font=ImageFont.truetype("malgun.ttf", 64))
        rows = OfflineOCR().read(fixture)
        print(json.dumps({"mode":"generated_offline_pixels", "game_capture":False, "rows":rows}, ensure_ascii=False, indent=2))
        return 0 if any("수락" in row["text"].replace(" ", "") for row in rows) else 2
    if args.command == "engine-check":
        from .game_observer import GameObserver
        from .ui_observer import SourceUiObserver
        from .action_port import SourceActionPort
        from .map_observer import MapObservationProvider
        from .current_context import CurrentContextReader
        from .ui_layout_observer import CurrentUILayoutReader
        from .quest_state_observer import QuestStateObservationProvider
        from .life_observer import LifeObservationProvider
        from .processing_observer import ProcessingObserver
        from .bag_recovery import BagRecoveryProvider
        from .arbeit_graph import audit
        from .source_integrity import check_sources
        result = audit()
        summary = {k:v for k,v in result.items() if k != "nodes"}
        summary["resource_integrity"] = check_sources()
        summary["adapters_imported"] = [c.__name__ for c in (GameObserver, SourceUiObserver, SourceActionPort, MapObservationProvider, CurrentContextReader, CurrentUILayoutReader, QuestStateObservationProvider, LifeObservationProvider, ProcessingObserver, BagRecoveryProvider)]
        summary["game_access"] = False
        print(json.dumps(summary, ensure_ascii=False, indent=2))
        return 0 if summary["all_definition_representation_passed"] else 2
    if args.command and args.command.startswith("arbeit-"):
        from .arbeit_runtime import readiness, PreparationSession, probe as read_probe, plan_phase
        if args.command == "arbeit-status":
            result = readiness()
        elif args.command == "arbeit-coverage":
            from .arbeit_coverage import coverage
            result = coverage()
        elif args.command == "arbeit-gaps":
            from .arbeit_gap_matrix import load_gap_matrix, filter_quest
            result = load_gap_matrix()
            if args.quest_id is not None:
                result = filter_quest(result, args.quest_id)
        elif args.command == "arbeit-impact":
            from .arbeit_coverage import update_impact
            result = update_impact(args.paths)
        elif args.command == "arbeit-graph":
            from .arbeit_graph import compile_all
            result = next((g for g in compile_all()["graphs"] if g["quest_id"] == args.quest_id), None)
            if result is None:
                parser.error("Quest ID not present in observed Arbeit definitions")
        elif args.command == "arbeit-audit":
            from .arbeit_graph import audit
            result = audit()
        elif args.command == 'arbeit-run':
            from .continuous_runner import ContinuousRunner
            driver = ContinuousRunner(PreparationSession(args.storage))
            driver.start(args.quest_id)
            try:
                while True:
                    result = driver.results.get()
                    print(json.dumps(result, ensure_ascii=False), flush=True)
                    if not result['continuous_runner']['observing']:
                        break
            except KeyboardInterrupt:
                driver.stop()
                result = {'execution':{'status':'paused','reason':'keyboard_interrupt'},'input_enabled':False}
            finally:
                driver.close()
                driver.closed.wait()
            return 0 if result['execution']['status'] == 'complete' else 2
        elif args.command == "arbeit-prepare":
            session = PreparationSession(args.storage)
            try:
                result = session.request_start(args.quest_id)
            finally:
                session.close()
        elif args.command == "arbeit-probe":
            result = read_probe(args.cli, args.evidence_dir or user_data() / "readonly-evidence")
        else:
            result = plan_phase(args.phase)
        print(json.dumps(result, ensure_ascii=False, indent=2))
        return 2 if args.command == "arbeit-prepare" else 0
    if args.command in (None, "gui", "ui-smoke"):
        from .ui import launch
        return launch(catalog, smoke=args.command == "ui-smoke")
    if args.command == "run":
        path = catalog.root / "data/fixtures" / (args.scenario + ".json")
        if args.scenario not in {p.stem for p in path.parent.glob("*.json")}:
            parser.error("Unknown synthetic scenario")
        journal = args.journal or user_data() / "last-run.json"
        if args.resume:
            engine = Executor.load(catalog, journal)
        else:
            # Never silently overwrite an old run. GUI also gives each new run
            # an independent journal; --resume is an explicit separate action.
            if journal.exists():
                parser.error("Journal exists; use --resume or select a new --journal")
            engine = Executor(catalog, read_json(path), journal)
        result = engine.run_until_boundary()
        print(json.dumps(result, ensure_ascii=False, indent=2))
        return 0 if engine.status == "simulated_complete" else 2
    if args.command == "check":
        from .engine import compile_goal
        goal = compile_goal(catalog, 7658096)
        print(json.dumps({"integrity": "passed", "version": DISPLAY_VERSION, "quests": len(catalog.quests),
            "gather_nodes": sum(len(g["nodes"]) for g in catalog.gather["groups"]), "recipes": len(catalog.processing["recipes"]),
            "destinations": len(catalog.movement["destinations"]), "goal": goal, "transport": "synthetic-only"}, ensure_ascii=False, indent=2))
    elif args.command == "search":
        print(json.dumps(catalog.search(args.query), ensure_ascii=False, indent=2))
    elif args.command == "coverage":
        print(json.dumps(catalog.coverage, ensure_ascii=False, indent=2))
    elif args.command == "impact":
        result = catalog.inspect_update(args.source, args.candidate) if args.candidate else catalog.impact([args.source])
        print(json.dumps(result, ensure_ascii=False, indent=2))
    return 0
