#!/usr/bin/env python3
"""
Crée les 60 agents MQTT OpenRemote pour lier les topics du simulateur
aux assets IOTSensor existants (realm smartcity).

Chaque agent MQTT:
- type: urn:openremote:agent:mqtt
- agentLink -> asset IOTSensor (avec location)
- topic: smartcity/{type}/{index} (1-indexé)

Utilise l'API REST OpenRemote avec auth admin.
"""

import json
import urllib.request
import urllib.error
import urllib.parse
import uuid
import sys
import time

OR_BASE = "http://localhost:8080"
OR_REALM = "smartcity"
OR_USER = "admin"
OR_PASS = "Digitribe972"

# Mapping des 60 capteurs simulateur -> noms d'assets OpenRemote
# Format: (type, index_1based) -> nom_partiel_asset
# Les assets ont des noms comme "Fort-de-France Centre (traffic)", "Fort-de-France Lamartine (airquality)", etc.
SENSOR_TOPIC_TO_ASSET = {
    # traffic (index 1-10)
    ("traffic", 1): "Fort-de-France Centre (traffic)",
    ("traffic", 2): "Le Lamentin Aéroport (traffic)",
    ("traffic", 3): "Le Robert D110 (traffic)",
    ("traffic", 4): "Sainte-Anne Plage (traffic)",
    ("traffic", 5): "Saint-Joseph D1 (traffic)",
    ("traffic", 6): "Trinité Centre",
    ("traffic", 7): "Le François D2",
    ("traffic", 8): "Ducos Penitencier",
    ("traffic", 9): "Schœlcher Morne",
    ("traffic", 10): "Case-Pilote Bourg",
    # airquality (index 1-10)
    ("airquality", 1): "Fort-de-France Lamartine (airquality)",
    ("airquality", 2): "Le Lamentin Zac (airquality)",
    ("airquality", 3): "Le Robert Bourg (airquality)",
    ("airquality", 4): "Sainte-Anne Village (airquality)",
    ("airquality", 5): "Saint-Joseph Morne (airquality)",
    ("airquality", 6): "Trinité Eglise",
    ("airquality", 7): "Le François Bourg",
    ("airquality", 8): "Ducos Centre",
    ("airquality", 9): "Schœlcher Plage",
    ("airquality", 10): "Case-Pilote D1",
    # parking (index 1-10)
    ("parking", 1): "Fort-de-France Place Clémenceau (parking)",
    ("parking", 2): "Le Lamentin Centre Commercial (parking)",
    ("parking", 3): "Le Robert Stade (parking)",
    ("parking", 4): "Sainte-Anne Mairie (parking)",
    ("parking", 5): "Saint-Joseph Ecole (parking)",
    ("parking", 6): "Trinité Port",
    ("parking", 7): "Le François Mairie",
    ("parking", 8): "Ducos ZI",
    ("parking", 9): "Schœlcher Bourg",
    ("parking", 10): "Case-Pilote Stade",
    # noise (index 1-10)
    ("noise", 1): "Fort-de-France Théâtre (noise)",
    ("noise", 2): "Le Lamentin Zone Industrielle (noise)",
    ("noise", 3): "Le Robert Bourg (noise)",
    ("noise", 4): "Sainte-Anne Plage (noise)",
    ("noise", 5): "Saint-Joseph Morne (noise)",
    ("noise", 6): "Trinité Centre",
    ("noise", 7): "Le François Bourg",
    ("noise", 8): "Ducos Penitencier",
    ("noise", 9): "Schœlcher Morne",
    ("noise", 10): "Case-Pilote Village",
    # weather (index 1-10)
    ("weather", 1): "Fort-de-France Meteo (weather)",
    ("weather", 2): "Le Lamentin Aéroport (weather)",
    ("weather", 3): "Le Robert Bourg (weather)",
    ("weather", 4): "Sainte-Anne Village (weather)",
    ("weather", 5): "Saint-Joseph Morne (weather)",
    ("weather", 6): "Trinité Eglise",
    ("weather", 7): "Le François Bourg",
    ("weather", 8): "Ducos Centre",
    ("weather", 9): "Schœlcher Plage",
    ("weather", 10): "Case-Pilote D1",
    # light (index 1-10)
    ("light", 1): "Fort-de-France Place (light)",
    ("light", 2): "Le Lamentin Rond-point (light)",
    ("light", 3): "Le Robert D110 (light)",
    ("light", 4): "Sainte-Anne Plage (light)",
    ("light", 5): "Saint-Joseph D1 (light)",
    ("light", 6): "Trinité Centre",
    ("light", 7): "Le François D2",
    ("light", 8): "Ducos Penitencier",
    ("light", 9): "Schœlcher Morne",
    ("light", 10): "Case-Pilote Bourg",
}


def http_request(url, method="GET", data=None, headers=None, auth=None):
    """Effectue une requête HTTP et retourne (status_code, body)"""
    req = urllib.request.Request(url, method=method)
    if headers:
        for k, v in headers.items():
            req.add_header(k, v)
    if auth:
        import base64
        creds = base64.b64encode(f"{auth[0]}:{auth[1]}".encode()).decode()
        req.add_header("Authorization", f"Basic {creds}")
    if data:
        req.data = json.dumps(data).encode("utf-8")
        req.add_header("Content-Type", "application/json")
    try:
        resp = urllib.request.urlopen(req, timeout=10)
        return resp.status, resp.read().decode("utf-8")
    except urllib.error.HTTPError as e:
        return e.code, e.read().decode("utf-8")
    except Exception as e:
        return 0, str(e)


def get_admin_token():
    """Obtient un token admin via l'API Keycloak"""
    url = f"{OR_BASE}/auth/realms/{OR_REALM}/protocol/openid-connect/token"
    data = urllib.parse.urlencode({
        "grant_type": "password",
        "client_id": "openremote",
        "username": OR_USER,
        "password": OR_PASS,
    }).encode()
    req = urllib.request.Request(url, data=data, method="POST")
    req.add_header("Content-Type", "application/x-www-form-urlencoded")
    try:
        resp = urllib.request.urlopen(req, timeout=10)
        body = json.loads(resp.read().decode())
        return body.get("access_token")
    except Exception as e:
        print(f"❌ Erreur auth: {e}")
        return None


def find_asset_by_name(token, name):
    """Recherche un asset par nom exact"""
    url = f"{OR_BASE}/api/{OR_REALM}/asset?name={urllib.parse.quote(name)}&limit=1"
    status, body = http_request(url, headers={"Authorization": f"Bearer {token}"})
    if status == 200:
        assets = json.loads(body)
        if assets:
            return assets[0]
    return None


def find_assets_by_name_prefix(token, prefix):
    """Recherche des assets par préfixe de nom"""
    url = f"{OR_BASE}/api/{OR_REALM}/asset?name={urllib.parse.quote(prefix)}&limit=20"
    status, body = http_request(url, headers={"Authorization": f"Bearer {token}"})
    if status == 200:
        return json.loads(body)
    return []


def create_mqtt_agent(token, agent_name, asset_id, topic):
    """Crée un agent MQTT et le lie à un asset via agentLink"""
    agent_id = str(uuid.uuid4())[:22]  # OpenRemote utilise des IDs tronqués
    agent = {
        "name": agent_name,
        "type": "urn:openremote:agent:mqtt",
        "realm": OR_REALM,
        "attributes": {
            "agentLink": {
                "name": "agentLink",
                "type": "JSON",
                "value": {"type": "AgentLink", "id": asset_id},
            },
            "topic": {
                "name": "topic",
                "type": "Text",
                "value": topic,
            },
        },
    }
    url = f"{OR_BASE}/api/{OR_REALM}/agent"
    status, body = http_request(
        url,
        method="POST",
        data=agent,
        headers={"Authorization": f"Bearer {token}"},
    )
    return status, body


def main():
    print("=" * 60)
    print("OpenRemote — Création des 60 agents MQTT")
    print("=" * 60)

    # 1. Authentification
    print("\n🔑 Authentification...")
    token = get_admin_token()
    if not token:
        print("❌ Impossible de s'authentifier. Vérifiez les credentials.")
        sys.exit(1)
    print("✅ Token obtenu")

    # 2. Créer les agents
    created = 0
    failed = 0
    skipped = 0

    for (sensor_type, index), asset_name in SENSOR_TOPIC_TO_ASSET.items():
        topic = f"smartcity/{sensor_type}/{index}"
        agent_name = f"MQTT Agent {asset_name}"

        print(f"\n[{created + failed + skipped + 1}/60] {sensor_type}/{index} → {asset_name}")

        # Chercher l'asset
        asset = find_asset_by_name(token, asset_name)
        if not asset:
            # Essayer sans le suffixe (type)
            short_name = asset_name.split(" (")[0]
            assets = find_assets_by_name_prefix(token, short_name)
            if assets:
                # Prendre celui avec location
                for a in assets:
                    attrs = a.get("attributes", {})
                    if attrs.get("location", {}).get("value", {}).get("coordinates"):
                        asset = a
                        break
                if not asset and assets:
                    asset = assets[0]

        if not asset:
            print(f"  ⚠️  Asset non trouvé: '{asset_name}', skipping")
            skipped += 1
            continue

        asset_id = asset["id"]
        print(f"  📍 Asset trouvé: {asset.get('name', '?')} ({asset_id})")

        # Créer l'agent MQTT
        status, body = create_mqtt_agent(token, agent_name, asset_id, topic)
        if status == 200:
            print(f"  ✅ Agent créé → topic: {topic}")
            created += 1
        elif status == 409:
            print(f"  ⏭️  Agent déjà existant, skipping")
            skipped += 1
        else:
            print(f"  ❌ Erreur {status}: {body[:200]}")
            failed += 1

        time.sleep(0.1)  # Pas surcharger l'API

    print(f"\n{'=' * 60}")
    print(f"📊 Résultat: {created} créés, {failed} erreurs, {skipped} skipped")
    print(f"{'=' * 60}")


if __name__ == "__main__":
    main()
