"""
Systematic exploration of ymspace.ga.nycu.edu.tw route.htm controller.
Probes various actions and parameters to extract routing/navigation data.
"""
import sys
import os
import json
import time
import urllib3
import requests
from pathlib import Path
from datetime import datetime

sys.stdout.reconfigure(encoding='utf-8')
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

BASE_URL = "https://ymspace.ga.nycu.edu.tw/gisweb/public/route.htm"
OUTPUT_DIR = Path(r"C:\Users\thc1006\Desktop\NQSD\新增資料夾\data\ymmap_archive\route_data")
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

SESSION = requests.Session()
SESSION.verify = False
SESSION.headers.update({
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
    "Accept": "application/json, text/html, */*",
    "Referer": "https://ymspace.ga.nycu.edu.tw/gisweb/public/route.htm",
})

DELAY = 0.2
stats = {
    "total_requests": 0,
    "successful": 0,
    "non_empty": 0,
    "errors": 0,
    "findings": [],
}


def make_request(params, label=""):
    """Make a request and return the response data."""
    stats["total_requests"] += 1
    try:
        resp = SESSION.get(BASE_URL, params=params, timeout=15)
        stats["successful"] += 1
        content_type = resp.headers.get("Content-Type", "")

        result = {
            "status_code": resp.status_code,
            "content_type": content_type,
            "content_length": len(resp.content),
            "params": params,
            "label": label,
        }

        # Try to parse as JSON
        try:
            data = resp.json()
            result["data"] = data
            result["is_json"] = True
        except (json.JSONDecodeError, ValueError):
            text = resp.text.strip()
            result["data"] = text
            result["is_json"] = False

        # Check if non-empty
        is_non_empty = False
        if result["is_json"]:
            if isinstance(result["data"], list) and len(result["data"]) > 0:
                is_non_empty = True
            elif isinstance(result["data"], dict) and len(result["data"]) > 0:
                is_non_empty = True
        elif isinstance(result["data"], str) and len(result["data"]) > 5:
            is_non_empty = True

        if is_non_empty:
            stats["non_empty"] += 1

        result["is_non_empty"] = is_non_empty
        return result

    except Exception as e:
        stats["errors"] += 1
        print(f"  ERROR: {e}")
        return {"error": str(e), "params": params, "label": label, "is_non_empty": False}


def save_result(filename, data):
    """Save data to a JSON file."""
    filepath = OUTPUT_DIR / filename
    with open(filepath, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)
    return filepath


def probe_actions():
    """Phase 1: Test various action names to discover endpoints."""
    print("=" * 70)
    print("PHASE 1: Probing action names")
    print("=" * 70)

    actions = [
        "findGeom", "findRoute", "findAll", "findById", "findByBuildId",
        "findByFloor", "findByName", "findByType", "findByCategory",
        "loadImageByApKey", "loadImage", "loadImages",
        "list", "listAll", "listBuildings", "listFloors", "listRooms",
        "getNetwork", "getGraph", "getPath", "getNodes", "getEdges",
        "getRoute", "getRoutes", "getBuilding", "getBuildings",
        "getFloor", "getFloors", "getRoom", "getRooms",
        "navigate", "navigation", "routing",
        "search", "query", "info",
        "getAP", "getAPs", "findAP", "findAPs", "listAP",
        "getWifi", "findWifi", "wifiMap",
        "getConnectivity", "getTopology", "getMap",
        "findNearby", "findNearest", "findPath",
        "getBuildingInfo", "getFloorInfo", "getRoomInfo",
        "getGeom", "getGeometry", "findGeometry",
        "loadRoute", "loadRoutes", "loadNetwork",
        "findNode", "findNodes", "findEdge", "findEdges",
        "getIndoorMap", "getOutdoorMap",
        "findPOI", "getPOI", "listPOI",
        "findFacility", "getFacility", "listFacility",
    ]

    discovered_actions = []

    for action in actions:
        print(f"  Testing action={action}...", end=" ")
        result = make_request({"action": action})
        time.sleep(DELAY)

        if result.get("is_non_empty"):
            print(f"HIT! (status={result.get('status_code')}, len={result.get('content_length')})")
            discovered_actions.append({
                "action": action,
                "status_code": result.get("status_code"),
                "content_length": result.get("content_length"),
                "content_type": result.get("content_type"),
                "is_json": result.get("is_json"),
                "data_preview": str(result.get("data", ""))[:200],
            })
            save_result(f"action_{action}.json", result.get("data"))
        else:
            sc = result.get("status_code", "ERR")
            cl = result.get("content_length", 0)
            print(f"empty/error (status={sc}, len={cl})")

    save_result("_discovered_actions.json", discovered_actions)
    print(f"\nDiscovered {len(discovered_actions)} working actions")
    return discovered_actions


def probe_findgeom_buildings():
    """Phase 2: Test findGeom with all building IDs."""
    print("\n" + "=" * 70)
    print("PHASE 2: findGeom with all building IDs")
    print("=" * 70)

    building_prefixes = {
        "B": range(1, 21),   # B001-B020
        "P": range(1, 10),   # P001-P009
        "Y": range(1, 20),   # Y001-Y019
    }

    results = {}

    for prefix, id_range in building_prefixes.items():
        for num in id_range:
            build_id = f"{prefix}{num:03d}"
            print(f"  findGeom buildId={build_id}...", end=" ")
            result = make_request({"action": "findGeom", "buildId": build_id}, label=f"findGeom_{build_id}")
            time.sleep(DELAY)

            if result.get("is_non_empty"):
                data = result.get("data")
                data_len = len(json.dumps(data)) if not isinstance(data, str) else len(data)
                print(f"HIT! (data_len={data_len})")
                results[build_id] = data
                save_result(f"findGeom_{build_id}.json", data)
            else:
                print("empty")

    save_result("_findGeom_buildings_summary.json", {
        "total_tested": sum(len(r) for r in building_prefixes.values()),
        "hits": list(results.keys()),
        "hit_count": len(results),
    })
    print(f"\nFound geometry data for {len(results)} buildings: {list(results.keys())}")
    return results


def probe_findgeom_floors(building_ids):
    """Phase 3: Test findGeom with building + floor combinations."""
    print("\n" + "=" * 70)
    print("PHASE 3: findGeom with building + floor combinations")
    print("=" * 70)

    floors = ["B2", "B1", "1F", "2F", "3F", "4F", "5F", "6F", "7F", "8F", "9F", "10F", "R1", "R2", "RF"]

    results = {}

    for build_id in building_ids:
        building_results = {}
        for floor in floors:
            print(f"  findGeom buildId={build_id} floor={floor}...", end=" ")
            result = make_request(
                {"action": "findGeom", "buildId": build_id, "floor": floor},
                label=f"findGeom_{build_id}_{floor}"
            )
            time.sleep(DELAY)

            if result.get("is_non_empty"):
                data = result.get("data")
                data_len = len(json.dumps(data)) if not isinstance(data, str) else len(data)
                print(f"HIT! (data_len={data_len})")
                building_results[floor] = data
                save_result(f"findGeom_{build_id}_{floor}.json", data)
            else:
                print("empty")

        if building_results:
            results[build_id] = list(building_results.keys())

    save_result("_findGeom_floors_summary.json", results)
    print(f"\nFound floor data for {len(results)} buildings")
    for bid, floors_found in results.items():
        print(f"  {bid}: {floors_found}")
    return results


def probe_loadimage_apkeys():
    """Phase 4: Test loadImageByApKey with IDs 1-200."""
    print("\n" + "=" * 70)
    print("PHASE 4: loadImageByApKey with IDs 1-200")
    print("=" * 70)

    results = {}

    for ap_id in range(1, 201):
        if ap_id % 20 == 0:
            print(f"  Progress: {ap_id}/200")

        result = make_request(
            {"action": "loadImageByApKey", "apKey": str(ap_id)},
            label=f"loadImageByApKey_{ap_id}"
        )
        time.sleep(DELAY)

        if result.get("is_non_empty"):
            data = result.get("data")
            print(f"  apKey={ap_id}: HIT! (len={result.get('content_length')})")
            results[str(ap_id)] = {
                "content_length": result.get("content_length"),
                "content_type": result.get("content_type"),
                "is_json": result.get("is_json"),
                "data_preview": str(data)[:300] if isinstance(data, str) else json.dumps(data, ensure_ascii=False)[:300],
            }
            # Save if JSON, otherwise note binary
            if result.get("is_json"):
                save_result(f"loadImageByApKey_{ap_id}.json", data)
            else:
                results[str(ap_id)]["note"] = "binary/image data"

    save_result("_loadImageByApKey_summary.json", {
        "total_tested": 200,
        "hits": list(results.keys()),
        "hit_count": len(results),
        "details": results,
    })
    print(f"\nFound AP data for {len(results)} keys")
    return results


def probe_findroute(building_ids):
    """Phase 5: Test findRoute with various building pairs."""
    print("\n" + "=" * 70)
    print("PHASE 5: findRoute with building pairs")
    print("=" * 70)

    results = {}

    # Test a subset of pairs (all pairs would be too many)
    test_pairs = []
    for i, b1 in enumerate(building_ids[:10]):
        for b2 in building_ids[i+1:i+4]:
            test_pairs.append((b1, b2))

    # Also test with different parameter names
    param_variants = [
        ("from", "to"),
        ("start", "end"),
        ("startBuildId", "endBuildId"),
        ("fromBuildId", "toBuildId"),
        ("origin", "destination"),
        ("srcBuildId", "dstBuildId"),
    ]

    # First, discover which parameter names work
    print("  Testing parameter name variants...")
    if len(building_ids) >= 2:
        b1, b2 = building_ids[0], building_ids[1]
        for p_from, p_to in param_variants:
            result = make_request(
                {"action": "findRoute", p_from: b1, p_to: b2},
                label=f"findRoute_params_{p_from}_{p_to}"
            )
            time.sleep(DELAY)
            if result.get("is_non_empty"):
                print(f"  Parameter variant works: {p_from}/{p_to}")
                save_result(f"findRoute_paramtest_{p_from}_{p_to}.json", result.get("data"))
                results[f"param_variant_{p_from}_{p_to}"] = True

    # Test with all discovered working variants plus defaults
    print("\n  Testing building pairs...")
    for b1, b2 in test_pairs:
        print(f"  findRoute {b1} -> {b2}...", end=" ")

        # Try multiple param combinations
        for p_from, p_to in [("from", "to"), ("startBuildId", "endBuildId"), ("fromBuildId", "toBuildId")]:
            result = make_request(
                {"action": "findRoute", p_from: b1, p_to: b2},
                label=f"findRoute_{b1}_{b2}"
            )
            time.sleep(DELAY)

            if result.get("is_non_empty"):
                data = result.get("data")
                print(f"HIT with {p_from}/{p_to}!")
                key = f"{b1}_to_{b2}"
                results[key] = data
                save_result(f"findRoute_{b1}_to_{b2}.json", data)
                break
        else:
            print("empty")

    save_result("_findRoute_summary.json", {
        "pairs_tested": len(test_pairs),
        "hits": [k for k, v in results.items() if not k.startswith("param_variant")],
        "working_params": [k for k, v in results.items() if k.startswith("param_variant")],
    })
    print(f"\nFound route data for {len(results)} queries")
    return results


def probe_findbyBuildId(building_ids):
    """Phase 6: Test findByBuildId with all building IDs."""
    print("\n" + "=" * 70)
    print("PHASE 6: findByBuildId with all building IDs")
    print("=" * 70)

    results = {}

    for build_id in building_ids:
        print(f"  findByBuildId buildId={build_id}...", end=" ")
        result = make_request(
            {"action": "findByBuildId", "buildId": build_id},
            label=f"findByBuildId_{build_id}"
        )
        time.sleep(DELAY)

        if result.get("is_non_empty"):
            data = result.get("data")
            print(f"HIT! (len={result.get('content_length')})")
            results[build_id] = data
            save_result(f"findByBuildId_{build_id}.json", data)
        else:
            print("empty")

    save_result("_findByBuildId_summary.json", {
        "hits": list(results.keys()),
        "hit_count": len(results),
    })
    print(f"\nFound building data for {len(results)} IDs")
    return results


def probe_additional_params():
    """Phase 7: Try additional parameter combinations on known working actions."""
    print("\n" + "=" * 70)
    print("PHASE 7: Additional parameter exploration")
    print("=" * 70)

    results = {}

    # Try findGeom with different param names
    extra_tests = [
        # findGeom variants
        {"action": "findGeom"},
        {"action": "findGeom", "type": "building"},
        {"action": "findGeom", "type": "room"},
        {"action": "findGeom", "type": "path"},
        {"action": "findGeom", "type": "route"},
        {"action": "findGeom", "type": "network"},
        {"action": "findGeom", "type": "node"},
        {"action": "findGeom", "type": "edge"},
        {"action": "findGeom", "type": "ap"},
        {"action": "findGeom", "type": "wifi"},
        {"action": "findGeom", "all": "true"},
        {"action": "findGeom", "campus": "yangming"},
        {"action": "findGeom", "campus": "YM"},

        # findRoute with floor params
        {"action": "findRoute", "from": "Y001", "to": "Y002", "fromFloor": "1F", "toFloor": "1F"},
        {"action": "findRoute", "startBuildId": "Y001", "endBuildId": "Y002", "startFloor": "1F", "endFloor": "1F"},

        # navigate action
        {"action": "navigate", "from": "Y001", "to": "Y002"},
        {"action": "navigate", "startBuildId": "Y001", "endBuildId": "Y002"},

        # Network/graph queries
        {"action": "getNetwork"},
        {"action": "getNetwork", "buildId": "Y001"},
        {"action": "getGraph"},
        {"action": "getGraph", "buildId": "Y001"},
        {"action": "getPath", "from": "Y001", "to": "Y002"},
        {"action": "getNodes"},
        {"action": "getNodes", "buildId": "Y001"},
        {"action": "getEdges"},
        {"action": "getEdges", "buildId": "Y001"},

        # Generic queries
        {"action": "findAll"},
        {"action": "findAll", "type": "building"},
        {"action": "findAll", "type": "ap"},
        {"action": "findAll", "type": "route"},
        {"action": "list"},
        {"action": "list", "type": "building"},
        {"action": "list", "type": "ap"},

        # Bare URL (no action)
        {},

        # AP related
        {"action": "findAP"},
        {"action": "findAP", "buildId": "Y001"},
        {"action": "getAP"},
        {"action": "getAP", "buildId": "Y001"},
        {"action": "loadImageByApKey"},
        {"action": "loadImageByApKey", "buildId": "Y001"},

        # Try POST-style params as GET
        {"action": "findGeom", "format": "json"},
        {"action": "findGeom", "format": "geojson"},
        {"action": "findGeom", "output": "json"},
    ]

    for i, params in enumerate(extra_tests):
        label = "_".join(f"{k}={v}" for k, v in params.items()) or "no_params"
        print(f"  [{i+1}/{len(extra_tests)}] {label}...", end=" ")
        result = make_request(params, label=label)
        time.sleep(DELAY)

        if result.get("is_non_empty"):
            data = result.get("data")
            data_str = json.dumps(data, ensure_ascii=False) if not isinstance(data, str) else data
            print(f"HIT! (len={len(data_str)})")
            results[label] = {
                "params": params,
                "content_length": result.get("content_length"),
                "data_preview": data_str[:500],
            }
            safe_filename = label.replace("=", "_").replace("/", "_").replace("?", "_")[:80]
            save_result(f"extra_{safe_filename}.json", data)
        else:
            print("empty")

    save_result("_extra_params_summary.json", results)
    print(f"\nFound {len(results)} additional hits")
    return results


def probe_ap_key_formats():
    """Phase 8: Try different AP key formats."""
    print("\n" + "=" * 70)
    print("PHASE 8: loadImageByApKey with various key formats")
    print("=" * 70)

    results = {}

    # Try building-style AP keys
    key_formats = []

    # Numeric ranges already covered in Phase 4
    # Try building-based keys
    for prefix in ["B", "P", "Y"]:
        for num in range(1, 20):
            key_formats.append(f"{prefix}{num:03d}")

    # Try MAC-address style keys (common for WiFi APs)
    # Skip these as they'd be too many permutations

    # Try string-based keys
    key_formats.extend([
        "AP001", "AP002", "AP003", "AP004", "AP005",
        "YM-AP001", "YM-AP002",
        "wifi-001", "wifi-002",
        "Y001-1F", "Y001-2F", "Y001-B1",
        "B001-1F", "B001-2F",
    ])

    for key in key_formats:
        result = make_request(
            {"action": "loadImageByApKey", "apKey": key},
            label=f"apKey_{key}"
        )
        time.sleep(DELAY)

        if result.get("is_non_empty"):
            data = result.get("data")
            print(f"  apKey={key}: HIT! (len={result.get('content_length')})")
            results[key] = {
                "content_length": result.get("content_length"),
                "content_type": result.get("content_type"),
            }
            if result.get("is_json"):
                save_result(f"apKey_{key}.json", data)

    save_result("_apKey_formats_summary.json", results)
    print(f"\nFound {len(results)} AP key hits with alternate formats")
    return results


def generate_summary():
    """Generate a comprehensive summary of all findings."""
    print("\n" + "=" * 70)
    print("GENERATING SUMMARY")
    print("=" * 70)

    # Count saved files
    saved_files = list(OUTPUT_DIR.glob("*.json"))
    data_files = [f for f in saved_files if not f.name.startswith("_")]
    summary_files = [f for f in saved_files if f.name.startswith("_")]

    summary = {
        "exploration_date": datetime.now().isoformat(),
        "base_url": BASE_URL,
        "statistics": stats,
        "saved_files": {
            "total": len(saved_files),
            "data_files": len(data_files),
            "summary_files": len(summary_files),
        },
        "findings": stats["findings"],
    }

    save_result("_EXPLORATION_SUMMARY.json", summary)

    print(f"\nTotal requests: {stats['total_requests']}")
    print(f"Successful: {stats['successful']}")
    print(f"Non-empty responses: {stats['non_empty']}")
    print(f"Errors: {stats['errors']}")
    print(f"Saved files: {len(saved_files)}")

    return summary


def main():
    print(f"Starting route.htm exploration at {datetime.now().isoformat()}")
    print(f"Base URL: {BASE_URL}")
    print(f"Output: {OUTPUT_DIR}")
    print()

    # Phase 1: Discover working actions
    discovered = probe_actions()

    # Phase 2: findGeom with all building IDs
    building_geom = probe_findgeom_buildings()
    building_ids = list(building_geom.keys())

    # Phase 3: findGeom with building + floor combos (only for buildings that returned data)
    if building_ids:
        floor_data = probe_findgeom_floors(building_ids)

    # Phase 4: loadImageByApKey with IDs 1-200
    ap_data = probe_loadimage_apkeys()

    # Phase 5: findRoute with building pairs
    all_building_ids = []
    for prefix in ["B", "P", "Y"]:
        for num in range(1, 21):
            all_building_ids.append(f"{prefix}{num:03d}")
    test_building_ids = building_ids if building_ids else all_building_ids[:10]
    route_data = probe_findroute(test_building_ids)

    # Phase 6: findByBuildId
    findby_data = probe_findbyBuildId(all_building_ids[:48])

    # Phase 7: Additional parameter exploration
    extra_data = probe_additional_params()

    # Phase 8: AP key format exploration
    ap_format_data = probe_ap_key_formats()

    # Generate summary
    generate_summary()

    print("\n" + "=" * 70)
    print("EXPLORATION COMPLETE")
    print("=" * 70)


if __name__ == "__main__":
    main()
