#!/usr/bin/env python3
"""
Cariflex - CitrineOS ↔ FlexMeasures Integration Service
Links OCPP charging sessions to FM sensors for energy management.
"""

import json, logging, os, re, sys
from datetime import datetime, timezone, timedelta
import requests

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("cariflex-citrineos-fm")

# Config
FM_HOST = os.getenv("FM_HOST", "https://cariflex.digitribe.fr")
FM_EMAIL = os.getenv("FM_EMAIL", "admin@digitribe.fr")
FM_PWD = os.getenv("FM_PWD", "Digitribe972")
CITRINEOS_HOST = os.getenv("CITRINEOS_HOST", "http://cariflex-citrineos-server:8080")

# OCPP Charge Point ID -> FM Sensor ID mapping
# Each charge point (EVSE) maps to a sensor in FlexMeasures
CHARGEPOINT_SENSOR_MAP = {
    "CP001": 61,  # EV_01
    "CP002": 62,  # EV_02
    "CP003": 63,  # EV_03
    "CP004": 64,  # EV_04
    "CP005": 65,  # EV_05
    "CP006": 66,  # EV_06
    "CP007": 67,  # EV_07
    "CP008": 68,  # EV_08
    "CP009": 69,  # EV_09
    "CP010": 70,  # EV_10
}

fm_session = None


def fm_login():
    global fm_session
    try:
        s = requests.Session(); s.verify = False
        r = s.get(f"{FM_HOST}/login", timeout=15)
        m = re.search(r"csrf_token[^>]*value=[\"\\']([^\"\\']+)", r.text)
        if m:
            r = s.post(f"{FM_HOST}/login", data={"email":FM_EMAIL,"password":FM_PWD,"csrf_token":m.group(1),"remember":"y"}, allow_redirects=True, timeout=15)
            if "dashboard" in r.url or r.status_code == 200:
                fm_session = s
                return True
    except Exception as e:
        logger.error(f"FM login: {e}")
    return False


def post_sensor_data(sensor_id, value, unit, start, duration="PT1H"):
    if not fm_session:
        return False
    try:
        r = fm_session.post(f"{FM_HOST}/api/v3_0/sensors/{sensor_id}/data",
            json={"values":[value],"start":start,"duration":duration,"unit":unit}, timeout=30)
        return r.status_code in [200,201,202]
    except Exception as e:
        logger.error(f"Post: {e}")
        return False


def get_citrineos_sessions():
    """Get active charging sessions from CitrineOS."""
    try:
        r = requests.get(f"{CITRINEOS_HOST}/api/v1/sessions", timeout=10)
        if r.status_code == 200:
            return r.json()
    except Exception as e:
        logger.error(f"CitrineOS sessions: {e}")
    return []


def get_citrineos_meter_values(session_id):
    """Get meter values for a charging session."""
    try:
        r = requests.get(f"{CITRINEOS_HOST}/api/v1/sessions/{session_id}/meter-values", timeout=10)
        if r.status_code == 200:
            return r.json()
    except Exception as e:
        logger.error(f"CitrineOS meter values: {e}")
    return []


def process_charging_session(session):
    """Process a charging session and update FM sensors."""
    charge_point_id = session.get("chargePointId")
    session_id = session.get("id")
    
    if charge_point_id not in CHARGEPOINT_SENSOR_MAP:
        logger.warning(f"Unknown charge point: {charge_point_id}")
        return
    
    fm_sensor_id = CHARGEPOINT_SENSOR_MAP[charge_point_id]
    
    # Get meter values
    meter_values = get_citrineos_meter_values(session_id)
    
    for mv in meter_values:
        value = mv.get("value", 0)
        unit = mv.get("unit", "W")
        timestamp = mv.get("timestamp", datetime.now(timezone.utc).isoformat())
        
        # Convert W to kW
        if unit == "W":
            value = value / 1000
            unit = "kW"
        
        # Post to FM
        ok = post_sensor_data(fm_sensor_id, value, unit, timestamp)
        logger.info(f"CitrineOS {charge_point_id} -> FM sensor {fm_sensor_id}: {value} {unit}: {'OK' if ok else 'FAIL'}")


def send_charging_profile(charge_point_id, power_limit_kw):
    """Send a charging profile to a charge point via CitrineOS."""
    try:
        payload = {
            "chargePointId": charge_point_id,
            "chargingProfile": {
                "chargingProfileId": 1,
                "stackLevel": 0,
                "chargingProfilePurpose": "TxDefaultProfile",
                "chargingProfileKind": "Absolute",
                "chargingSchedule": {
                    "chargingRateUnit": "W",
                    "chargingSchedulePeriod": [{
                        "startPeriod": 0,
                        "limit": int(power_limit_kw * 1000)  # Convert kW to W
                    }]
                }
            }
        }
        r = requests.post(f"{CITRINEOS_HOST}/api/v1/charge-points/{charge_pointId}/charging-profiles",
            json=payload, timeout=10)
        logger.info(f"Charging profile sent to {charge_point_id}: {power_limit_kw} kW -> {r.status_code}")
        return r.status_code == 200
    except Exception as e:
        logger.error(f"Send charging profile: {e}")
        return False


def process_fm_schedules():
    """Read FM schedules and send to CitrineOS charge points."""
    for cp_id, sensor_id in CHARGEPOINT_SENSOR_MAP.items():
        try:
            # Get latest schedule from FM
            r = fm_session.get(f"{FM_HOST}/api/v3_0/sensors/{sensor_id}/data?limit=1", timeout=10)
            if r.status_code == 200:
                data = r.json()
                if data and len(data) > 0:
                    latest = data[-1]
                    power = float(latest.get("event_value", 0))
                    if power > 0:
                        send_charging_profile(cp_id, power)
        except Exception as e:
            logger.error(f"FM schedule for {cp_id}: {e}")


async def main():
    logger.info("Cariflex CitrineOS-FM Integration starting")
    logger.info(f"FM: {FM_HOST}")
    logger.info(f"CitrineOS: {CITRINEOS_HOST}")
    logger.info(f"Charge points: {len(CHARGEPOINT_SENSOR_MAP)}")
    
    fm_login()
    
    while True:
        try:
            # 1. Get charging sessions from CitrineOS
            sessions = get_citrineos_sessions()
            for session in sessions:
                if session.get("status") == "Active":
                    process_charging_session(session)
            
            # 2. Send FM schedules to CitrineOS
            process_fm_schedules()
            
        except Exception as e:
            logger.error(f"Integration error: {e}")
            fm_login()
        
        await asyncio.sleep(30)


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