"""
Simulador AIS - Tráfico marítimo zona Miami / Port of Miami
Genera barcos y ayudas a la navegación ficticios para pruebas.
"""
import asyncio
import random
import math
from datetime import datetime

# Ayudas a la navegación reales zona Miami (posiciones aproximadas)
MIAMI_AIDS = [
    {"id": "BOYA_MIAMI_01", "nombre": "Boya Canal Principal N", "tipo": "BOYA_LATERAL",
     "categoria": "FLOTANTE", "tipo_ais": "AIS_NORMAL", "mmsi": "338001001",
     "lat_nominal": 25.7730, "lon_nominal": -80.1320, "radio_borneo_m": 10.0},
    {"id": "BOYA_MIAMI_02", "nombre": "Boya Canal Principal S", "tipo": "BOYA_LATERAL",
     "categoria": "FLOTANTE", "tipo_ais": "ATON_21", "mmsi": "338001002",
     "lat_nominal": 25.7650, "lon_nominal": -80.1290, "radio_borneo_m": 10.0},
    {"id": "BOYA_MIAMI_03", "nombre": "Boya Cardinal Este", "tipo": "BOYA_CARDINAL",
     "categoria": "FLOTANTE", "tipo_ais": "SIN_AIS", "mmsi": None,
     "lat_nominal": 25.7800, "lon_nominal": -80.1200, "radio_borneo_m": 10.0},
    {"id": "FARO_MIAMI_01", "nombre": "Faro Cape Florida", "tipo": "FARO",
     "categoria": "FIJA", "tipo_ais": "SIN_AIS", "mmsi": None,
     "lat_nominal": 25.6664, "lon_nominal": -80.1572, "radio_borneo_m": 0.0,
     "caracteristica_luz": "Fl W 15s", "alcance_nm": 12},
    {"id": "FAROL_MIAMI_01", "nombre": "Farol Entrada Puerto", "tipo": "FAROL",
     "categoria": "FIJA", "tipo_ais": "SIN_AIS", "mmsi": None,
     "lat_nominal": 25.7743, "lon_nominal": -80.1337, "radio_borneo_m": 0.0,
     "caracteristica_luz": "Fl(2) R 10s", "alcance_nm": 6},
]

# Barcos simulados en zona Miami.
# Dimensiones (length × beam, to_bow/to_stern/to_port/to_starboard) según AIS Type 5/24.
# Reference point = posición de la antena AIS.
MIAMI_VESSELS = [
    {"mmsi": "636015000", "nombre": "CARNIVAL MAGIC",     "tipo": 60, "destino": "MIAMI",
     "lat": 25.7800, "lon": -80.1900, "sog": 0.2,  "cog": 90,  "heading": 90,
     "length": 306, "beam": 37, "to_bow": 200, "to_stern": 106, "to_port": 18, "to_starboard": 19,
     "imo": "9378474",  "callsign": "3FOF8",  "bandera": "PA",
     "draught": 8.2,  "eta": "05-15 14:00", "nav_status": 5,  "fix_type": 1, "rot": 0.0},
    {"mmsi": "353984000", "nombre": "MSC SEASHORE",       "tipo": 60, "destino": "NASSAU",
     "lat": 25.7600, "lon": -80.2100, "sog": 14.5, "cog": 125, "heading": 124,
     "length": 339, "beam": 41, "to_bow": 220, "to_stern": 119, "to_port": 20, "to_starboard": 21,
     "imo": "9839348",  "callsign": "3EUX9",  "bandera": "PA",
     "draught": 8.5,  "eta": "05-12 08:00", "nav_status": 0,  "fix_type": 1, "rot": 0.5},
    {"mmsi": "244820000", "nombre": "BISCAYNE BAY TUG",   "tipo": 52, "destino": "PORT MIAMI",
     "lat": 25.7720, "lon": -80.1600, "sog": 5.2,  "cog": 270, "heading": 268,
     "length": 30,  "beam": 11, "to_bow": 18,  "to_stern": 12,  "to_port": 5,  "to_starboard": 6,
     "imo": "9412104",  "callsign": "WDF8493", "bandera": "US",
     "draught": 4.0,  "eta": "05-10 18:30", "nav_status": 0,  "fix_type": 1, "rot": 0.0},
    {"mmsi": "367123450", "nombre": "SEA HUNTER",         "tipo": 30, "destino": "FISHING",
     "lat": 25.7400, "lon": -80.1100, "sog": 3.1,  "cog": 45,  "heading": 47,
     "length": 18,  "beam": 6,  "to_bow": 12,  "to_stern": 6,   "to_port": 3,  "to_starboard": 3,
     "imo": None,       "callsign": "WDA3047", "bandera": "US",
     "draught": 1.8,  "eta": None,         "nav_status": 7,  "fix_type": 1, "rot": 0.0},
    {"mmsi": "311001234", "nombre": "BAHAMAS EXPRESS",    "tipo": 69, "destino": "FREEPORT",
     "lat": 25.7900, "lon": -80.1800, "sog": 18.0, "cog": 65,  "heading": 65,
     "length": 80,  "beam": 14, "to_bow": 60,  "to_stern": 20,  "to_port": 7,  "to_starboard": 7,
     "imo": "9242315",  "callsign": "C6BX2",  "bandera": "BS",
     "draught": 3.6,  "eta": "05-11 06:00", "nav_status": 0,  "fix_type": 1, "rot": 0.0},
]


# Ship-type table — AIS spec, message type 5 cargo type field (rangos parcial).
SHIP_TYPE_NAMES = {
    0: "Not available", 20: "WIG", 21: "WIG hazardous A", 22: "WIG hazardous B",
    23: "WIG hazardous C", 24: "WIG hazardous D", 30: "Fishing", 31: "Towing",
    32: "Towing >200m", 33: "Dredging", 34: "Diving", 35: "Military",
    36: "Sailing", 37: "Pleasure craft", 40: "High speed craft",
    50: "Pilot vessel", 51: "Search and rescue", 52: "Tug", 53: "Port tender",
    54: "Anti-pollution", 55: "Law enforcement", 58: "Medical transport",
    60: "Passenger", 61: "Passenger hazardous A", 62: "Passenger hazardous B",
    69: "Passenger (other)",
    70: "Cargo", 71: "Cargo hazardous A", 72: "Cargo hazardous B",
    73: "Cargo hazardous C", 74: "Cargo hazardous D", 79: "Cargo (other)",
    80: "Tanker", 81: "Tanker hazardous A", 82: "Tanker hazardous B",
    83: "Tanker hazardous C", 84: "Tanker hazardous D", 89: "Tanker (other)",
    90: "Other", 99: "Other (no info)",
}

# AIS navigation status codes (Type 1/2/3 nav_status field)
NAV_STATUS_NAMES = {
    0: "Under way using engine", 1: "At anchor", 2: "Not under command",
    3: "Restricted manoeuvrability", 4: "Constrained by draught", 5: "Moored",
    6: "Aground", 7: "Engaged in fishing", 8: "Under way sailing",
    9: "Reserved (HSC)", 10: "Reserved (WIG)", 11: "Power-driven towing astern",
    12: "Power-driven pushing ahead", 13: "Reserved", 14: "AIS-SART/MOB/EPIRB",
    15: "Undefined / default",
}


def estimate_dims_from_type(ship_type: int) -> tuple[int, int]:
    """Fallback length/beam (m) for vessels reporting position (Type 1/18)
    but no Type 5/24 with real dimensions yet. Conservative averages."""
    t = ship_type or 0
    if 80 <= t < 90: return (220, 32)   # tanker
    if 70 <= t < 80: return (180, 28)   # cargo
    if 60 <= t < 70: return (200, 32)   # passenger
    if t == 36:      return (12,  4)    # sailing
    if t == 30:      return (18,  6)    # fishing
    if t == 31 or t == 32 or t == 52: return (30, 10)  # tug / dredger
    if t == 37:      return (15,  5)    # pleasure
    return (50, 10)

def haversine_distance(lat1, lon1, lat2, lon2):
    R = 6371000
    phi1, phi2 = math.radians(lat1), math.radians(lat2)
    dphi = math.radians(lat2 - lat1)
    dlambda = math.radians(lon2 - lon1)
    a = math.sin(dphi/2)**2 + math.cos(phi1)*math.cos(phi2)*math.sin(dlambda/2)**2
    return R * 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))

def move_vessel(vessel, dt_seconds=5):
    if vessel["sog"] < 0.5:
        return vessel
    speed_ms = vessel["sog"] * 0.514444
    distance_m = speed_ms * dt_seconds
    cog_rad = math.radians(vessel["cog"])
    dlat = (distance_m * math.cos(cog_rad)) / 111320
    dlon = (distance_m * math.sin(cog_rad)) / (111320 * math.cos(math.radians(vessel["lat"])))
    vessel["lat"] += dlat
    vessel["lon"] += dlon
    vessel["cog"] += random.uniform(-1.5, 1.5)
    vessel["cog"] %= 360
    vessel["heading"] = vessel["cog"]
    return vessel

def simulate_aid_borneo(aid):
    if aid["radio_borneo_m"] == 0 or aid["tipo_ais"] == "SIN_AIS":
        return aid["lat_nominal"], aid["lon_nominal"]
    offset_m = random.uniform(0, aid["radio_borneo_m"])
    angle = random.uniform(0, 360)
    dlat = (offset_m * math.cos(math.radians(angle))) / 111320
    dlon = (offset_m * math.sin(math.radians(angle))) / (111320 * math.cos(math.radians(aid["lat_nominal"])))
    return aid["lat_nominal"] + dlat, aid["lon_nominal"] + dlon

async def run_simulator(broadcast_callback):
    vessels = [v.copy() for v in MIAMI_VESSELS]
    aids = [a.copy() for a in MIAMI_AIDS]

    while True:
        now = datetime.utcnow().isoformat()

        # Mover barcos
        for v in vessels:
            v = move_vessel(v, dt_seconds=5)
            await broadcast_callback({
                "type": "vessel",
                "mmsi": v["mmsi"],
                "nombre": v["nombre"],
                "tipo": v["tipo"],
                "tipo_nombre":  SHIP_TYPE_NAMES.get(v["tipo"], f"Type {v['tipo']}"),
                "destino": v["destino"],
                "lat": round(v["lat"], 6),
                "lon": round(v["lon"], 6),
                "sog": v["sog"],
                "cog": round(v["cog"], 1),
                "heading": round(v["heading"], 1),
                # Static AIS Type 5 / Type 24
                "imo":          v.get("imo"),
                "callsign":     v.get("callsign"),
                "bandera":      v.get("bandera"),
                "length":       v.get("length"),
                "beam":         v.get("beam"),
                "to_bow":       v.get("to_bow"),
                "to_stern":     v.get("to_stern"),
                "to_port":      v.get("to_port"),
                "to_starboard": v.get("to_starboard"),
                "draught":      v.get("draught"),
                "eta":          v.get("eta"),
                # Dynamic Type 1/2/3
                "nav_status":   v.get("nav_status"),
                "nav_status_name": NAV_STATUS_NAMES.get(v.get("nav_status", 15), "Unknown"),
                "rot":          v.get("rot"),
                "fix_type":     v.get("fix_type"),
                "timestamp": now
            })

        # Actualizar ayudas con AIS
        for a in aids:
            if a["tipo_ais"] == "SIN_AIS":
                continue
            lat_act, lon_act = simulate_aid_borneo(a)
            desplazamiento = haversine_distance(a["lat_nominal"], a["lon_nominal"], lat_act, lon_act)
            await broadcast_callback({
                "type": "aid_position",
                "id": a["id"],
                "mmsi": a["mmsi"],
                "lat_actual": round(lat_act, 6),
                "lon_actual": round(lon_act, 6),
                "desplazamiento_m": round(desplazamiento, 2),
                "en_posicion": desplazamiento <= 15.0,
                "timestamp": now
            })

        await asyncio.sleep(5)
