from typing import Any, List, Optional
from datetime import datetime, timedelta
import json
import os
import shutil
import threading
from fastapi import APIRouter, HTTPException, File, UploadFile, Query
from fastapi.responses import PlainTextResponse
import app.services.simulation as simulation
import app.services.globals as globals
from app.infra.cache.redis_client import redis_client
from app.services.tjnetwork import (
    run_project,
    run_project_return_dict,
    run_inp,
    dump_output,
)
from app.algorithms.simulation.scenarios import (
    burst_analysis,
    valve_close_analysis,
    flushing_analysis,
    contaminant_simulation,
    age_analysis,
    # scheduling_analysis,
    pressure_regulation,
)
from app.algorithms.sensor import (
    pressure_sensor_placement_sensitivity,
    pressure_sensor_placement_kmeans,
)
from app.services.network_import import network_update
from app.services.simulation_ops import (
    project_management,
    scheduling_simulation,
    daily_scheduling_simulation,
)
from app.services.valve_isolation import analyze_valve_isolation
from pydantic import BaseModel

router = APIRouter()


class RunSimulationManuallyByDate(BaseModel):
    name: str
    simulation_date: str
    start_time: str
    duration: int


class BurstAnalysis(BaseModel):
    name: str
    modify_pattern_start_time: str
    burst_ID: List[str] | str | None = None
    burst_size: List[float] | float | int | None = None
    modify_total_duration: int = 900
    modify_fixed_pump_pattern: Optional[dict[str, list]] = None
    modify_variable_pump_pattern: Optional[dict[str, list]] = None
    modify_valve_opening: Optional[dict[str, float]] = None
    scheme_name: Optional[str] = None


class SchedulingAnalysis(BaseModel):
    network: str
    start_time: str
    pump_control: dict
    tank_id: str
    water_plant_output_id: str
    time_delta: Optional[int] = 300


class PressureRegulation(BaseModel):
    network: str
    start_time: str
    pump_control: dict
    tank_init_level: Optional[dict] = None
    duration: Optional[int] = 900
    scheme_name: Optional[str] = None


class ProjectManagement(BaseModel):
    network: str
    start_time: str
    pump_control: dict
    tank_init_level: Optional[dict] = None
    region_demand: Optional[dict] = None


class DailySchedulingAnalysis(BaseModel):
    network: str
    start_time: str
    pump_control: dict
    reservoir_id: str
    tank_id: str
    water_plant_output_id: str
    time_delta: Optional[int] = 300


class PumpFailureState(BaseModel):
    time: str
    pump_status: dict


class PressureSensorPlacement(BaseModel):
    name: str
    scheme_name: str
    sensor_number: int
    min_diameter: int = 0
    username: str


def run_simulation_manually_by_date(
    network_name: str, base_date: datetime, start_time: str, duration: int
) -> None:
    time_parts = list(map(int, start_time.split(":")))
    if len(time_parts) == 2:
        start_hour, start_minute = time_parts
        start_second = 0
    elif len(time_parts) == 3:
        start_hour, start_minute, start_second = time_parts
    else:
        raise ValueError("Invalid start_time format. Use HH:MM or HH:MM:SS")

    start_datetime = base_date.replace(
        hour=start_hour, minute=start_minute, second=start_second
    )
    end_datetime = start_datetime + timedelta(minutes=duration)
    current_time = start_datetime
    while current_time < end_datetime:
        iso_time = current_time.strftime("%Y-%m-%dT%H:%M:%S") + "+08:00"
        simulation.run_simulation(
            name=network_name,
            simulation_type="realtime",
            modify_pattern_start_time=iso_time,
        )
        current_time += timedelta(minutes=15)


# 必须用这个PlainTextResponse，不然每个key都有引号
@router.get("/runproject/", response_class=PlainTextResponse)
async def run_project_endpoint(network: str) -> str:
    lock_key = "exclusive_api_lock"
    timeout = 120  # 锁自动过期时间（秒）

    # 尝试获取锁（NX=True: 不存在时设置，EX=timeout: 过期时间）
    acquired = redis_client.set(lock_key, "locked", nx=True, ex=timeout)

    if not acquired:
        raise HTTPException(status_code=409, detail="is in simulation")
    else:
        try:
            return run_project(network)
        finally:
            # 手动释放锁（可选，依赖过期时间自动释放更安全）
            redis_client.delete(lock_key)


# DingZQ, 2025-02-04, 返回dict[str, Any]
# output 和 report
# output 是 json
# report 是 text
@router.get("/runprojectreturndict/")
async def run_project_return_dict_endpoint(network: str) -> dict[str, Any]:
    lock_key = "exclusive_api_lock"
    timeout = 120  # 锁自动过期时间（秒）

    # 尝试获取锁（NX=True: 不存在时设置，EX=timeout: 过期时间）
    acquired = redis_client.set(lock_key, "locked", nx=True, ex=timeout)

    if not acquired:
        raise HTTPException(status_code=409, detail="is in simulation")
    else:
        try:
            return run_project_return_dict(network)
        finally:
            # 手动释放锁（可选，依赖过期时间自动释放更安全）
            redis_client.delete(lock_key)


# put in inp folder, name without extension
@router.get("/runinp/")
async def run_inp_endpoint(network: str) -> str:
    return run_inp(network)


# path is absolute path
@router.get("/dumpoutput/")
async def dump_output_endpoint(output: str) -> str:
    return dump_output(output)


# Analysis Endpoints
@router.get("/burstanalysis/")
async def burst_analysis_endpoint(
    network: str, pipe_id: str, start_time: str, end_time: str, burst_flow: float
):
    return burst_analysis(network, pipe_id, start_time, end_time, burst_flow)


@router.get("/burst_analysis/")
async def fastapi_burst_analysis(
    network: str = Query(...),
    modify_pattern_start_time: str = Query(...),
    burst_ID: list[str] = Query(...),
    burst_size: list[float] = Query(...),
    modify_total_duration: int = Query(...),
    scheme_name: str = Query(...),
) -> str:
    burst_analysis(
        name=network,
        modify_pattern_start_time=modify_pattern_start_time,
        burst_ID=burst_ID,
        burst_size=burst_size,
        modify_total_duration=modify_total_duration,
        scheme_name=scheme_name,
    )
    return "success"


@router.get("/valvecloseanalysis/")
async def valve_close_analysis_endpoint(
    network: str, valve_id: str, start_time: str, end_time: str
):
    return valve_close_analysis(network, valve_id, start_time, end_time)


@router.get("/valve_close_analysis/", response_class=PlainTextResponse)
async def fastapi_valve_close_analysis(
    network: str,
    start_time: str,
    valves: List[str] = Query(...),
    duration: int | None = None,
) -> str:
    result = valve_close_analysis(
        name=network,
        modify_pattern_start_time=start_time,
        modify_total_duration=duration or 900,
        modify_valve_opening={valve_id: 0.0 for valve_id in valves},
    )
    return result or "success"


@router.get("/valve_isolation_analysis/")
async def valve_isolation_endpoint(
    network: str,
    accident_element: List[str] = Query(...),
    disabled_valves: List[str] = Query(None),
):
    result = {
        "accident_element": "P461309",
        "accident_elements": ["P461309"],
        "affected_nodes": [
            "J316629_A",
            "J317037_B",
            "J317060_B",
            "J408189_B",
            "J499996",
            "J524940",
            "J535933",
            "J58841",
        ],
        "isolatable": True,
        "must_close_valves": ["210521658", "V12974", "V12986", "V12993"],
        "optional_valves": [],
    }
    result = analyze_valve_isolation(network, accident_element, disabled_valves)
    return result


@router.get("/flushinganalysis/")
async def flushing_analysis_endpoint(
    network: str, pipe_id: str, start_time: str, duration: float, flow: float
):
    return flushing_analysis(network, pipe_id, start_time, duration, flow)


@router.get("/flushing_analysis/", response_class=PlainTextResponse)
async def fastapi_flushing_analysis(
    network: str,
    start_time: str,
    valves: List[str] = Query(...),
    valves_k: List[float] = Query(...),
    drainage_node_ID: str = Query(...),
    flush_flow: float = 0,
    duration: int | None = None,
    scheme_name: str | None = None,
) -> str:
    valve_opening = {
        valve_id: float(valves_k[idx]) for idx, valve_id in enumerate(valves)
    }
    result = flushing_analysis(
        name=network,
        modify_pattern_start_time=start_time,
        modify_total_duration=duration or 900,
        modify_valve_opening=valve_opening,
        drainage_node_ID=drainage_node_ID,
        flushing_flow=flush_flow,
        scheme_name=scheme_name,
    )
    return result or "success"


@router.get("/contaminant_simulation/", response_class=PlainTextResponse)
async def fastapi_contaminant_simulation(
    network: str,
    start_time: str,
    source: str,
    concentration: float,
    duration: int,
    scheme_name: str | None = None,
    pattern: str | None = None,
) -> str:
    result = contaminant_simulation(
        name=network,
        modify_pattern_start_time=start_time,
        scheme_name=scheme_name,
        modify_total_duration=duration,
        source=source,
        concentration=concentration,
        source_pattern=pattern,
    )
    return result or "success"


@router.get("/ageanalysis/")
async def age_analysis_endpoint(network: str):
    return age_analysis(network)


@router.get("/age_analysis/", response_class=PlainTextResponse)
async def fastapi_age_analysis(
    network: str, start_time: str, end_time: str, duration: int
) -> str:
    result = age_analysis(network, start_time, duration)
    return result or "success"


# @router.get("/schedulinganalysis/")
# async def scheduling_analysis_endpoint(network: str):
#     return scheduling_analysis(network)


@router.get("/pressureregulation/")
async def pressure_regulation_endpoint(
    network: str, target_node: str, target_pressure: float
):
    return pressure_regulation(network, target_node, target_pressure)


@router.post("/pressure_regulation/")
async def fastapi_pressure_regulation(data: PressureRegulation) -> str:
    item = data.dict()
    simulation.query_corresponding_element_id_and_query_id(item["network"])
    fixed_pumps = set(globals.fixed_pumps_id.keys())
    variable_pumps = set(globals.variable_pumps_id.keys())
    fixed_pump_pattern: dict[str, list] = {}
    variable_pump_pattern: dict[str, list] = {}
    for pump_id, values in item["pump_control"].items():
        if pump_id in variable_pumps:
            variable_pump_pattern[pump_id] = values
        else:
            fixed_pump_pattern[pump_id] = values
    pressure_regulation(
        name=item["network"],
        modify_pattern_start_time=item["start_time"],
        modify_total_duration=item["duration"] or 900,
        modify_tank_initial_level=item["tank_init_level"],
        modify_fixed_pump_pattern=fixed_pump_pattern or None,
        modify_variable_pump_pattern=variable_pump_pattern or None,
        scheme_name=item["scheme_name"],
    )
    return "success"


@router.get("/projectmanagement/")
async def project_management_endpoint(network: str):
    return project_management(network)


@router.post("/project_management/")
async def fastapi_project_management(data: ProjectManagement) -> str:
    item = data.dict()
    return project_management(
        prj_name=item["network"],
        start_datetime=item["start_time"],
        pump_control=item["pump_control"],
        tank_initial_level_control=item["tank_init_level"],
        region_demand_control=item["region_demand"],
    )


# @router.get("/dailyschedulinganalysis/")
# async def daily_scheduling_analysis_endpoint(network: str):
#     return daily_scheduling_analysis(network)


@router.post("/scheduling_analysis/")
async def fastapi_scheduling_analysis(data: SchedulingAnalysis) -> str:
    item = data.dict()
    return scheduling_simulation(
        item["network"],
        item["start_time"],
        item["pump_control"],
        item["tank_id"],
        item["water_plant_output_id"],
        item["time_delta"],
    )


@router.post("/daily_scheduling_analysis/")
async def fastapi_daily_scheduling_analysis(data: DailySchedulingAnalysis) -> str:
    item = data.dict()
    return daily_scheduling_simulation(
        item["network"],
        item["start_time"],
        item["pump_control"],
        item["reservoir_id"],
        item["tank_id"],
        item["water_plant_output_id"],
    )


@router.post("/network_project/")
async def fastapi_network_project(file: UploadFile = File()) -> str:
    temp_file_dir = "./inp/"
    if not os.path.exists(temp_file_dir):
        os.mkdir(temp_file_dir)
    temp_file_name = f'network_project_{datetime.now().strftime("%Y%m%d")}'
    temp_file_path = f"{temp_file_dir}{temp_file_name}.inp"
    with open(temp_file_path, "wb") as buffer:
        shutil.copyfileobj(file.file, buffer)
    return run_inp(temp_file_name)


@router.get("/networkupdate/")
async def network_update_endpoint(network: str):
    return network_update(network)


@router.post("/network_update/")
async def fastapi_network_update(file: UploadFile = File()) -> str:
    default_folder = "./"
    temp_file_name = f'network_update_{datetime.now().strftime("%Y%m%d")}'
    temp_file_path = os.path.join(default_folder, temp_file_name)
    try:
        with open(temp_file_path, "wb") as buffer:
            shutil.copyfileobj(file.file, buffer)
        network_update(temp_file_path)
        return json.dumps({"message": "管网更新成功"})
    except Exception as exc:
        raise HTTPException(status_code=500, detail=f"数据库操作失败: {exc}")


# @router.get("/pumpfailure/")
# async def pump_failure_endpoint(network: str, pump_id: str, time: str):
#     return pump_failure(network, pump_id, time)


@router.post("/pump_failure/")
async def fastapi_pump_failure(data: PumpFailureState) -> str:
    item = data.dict()
    with open("./pump_failure_message.txt", "a", encoding="utf-8-sig") as f1:
        f1.write("[{}] {}\n".format(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), item))
    with open("./pump_failure_status.txt", "r", encoding="utf-8-sig") as f2:
        lines = f2.readlines()
        first_stage_pump_status_dict = json.loads(json.dumps(eval(lines[0])))
        second_stage_pump_status_dict = json.loads(json.dumps(eval(lines[-1])))
        pump_status_dict = {
            "first": first_stage_pump_status_dict,
            "second": second_stage_pump_status_dict,
        }
    status_info = item.copy()
    for pump_type in status_info["pump_status"].keys():
        if pump_type in pump_status_dict.keys():
            if all(
                pump_id in pump_status_dict[pump_type].keys()
                for pump_id in status_info["pump_status"][pump_type].keys()
            ):
                for pump_id in status_info["pump_status"][pump_type].keys():
                    pump_status_dict[pump_type][pump_id] = int(
                        status_info["pump_status"][pump_type][pump_id]
                    )
            else:
                return json.dumps("ERROR: Wrong Pump ID")
        else:
            return json.dumps("ERROR: Wrong Pump Type")
    with open("./pump_failure_status.txt", "w", encoding="utf-8-sig") as f2_:
        f2_.write(
            "{}\n{}".format(pump_status_dict["first"], pump_status_dict["second"])
        )
    return json.dumps("SUCCESS")


@router.get("/pressuresensorplacementsensitivity/")
async def pressure_sensor_placement_sensitivity_endpoint(
    name: str, scheme_name: str, sensor_number: int, min_diameter: int, username: str
):
    return pressure_sensor_placement_sensitivity(
        name, scheme_name, sensor_number, min_diameter, username
    )


@router.post("/pressure_sensor_placement_sensitivity/")
async def fastapi_pressure_sensor_placement_sensitivity(
    data: PressureSensorPlacement,
) -> None:
    item = data.dict()
    pressure_sensor_placement_sensitivity(
        name=item["name"],
        scheme_name=item["scheme_name"],
        sensor_number=item["sensor_number"],
        min_diameter=item["min_diameter"],
        username=item["username"],
    )


@router.get("/pressuresensorplacementkmeans/")
async def pressure_sensor_placement_kmeans_endpoint(
    name: str, scheme_name: str, sensor_number: int, min_diameter: int, username: str
):
    return pressure_sensor_placement_kmeans(
        name, scheme_name, sensor_number, min_diameter, username
    )


@router.post("/pressure_sensor_placement_kmeans/")
async def fastapi_pressure_sensor_placement_kmeans(
    data: PressureSensorPlacement,
) -> None:
    item = data.dict()
    pressure_sensor_placement_kmeans(
        name=item["name"],
        scheme_name=item["scheme_name"],
        sensor_number=item["sensor_number"],
        min_diameter=item["min_diameter"],
        username=item["username"],
    )


@router.post("/sensorplacementscheme/create")
async def fastapi_pressure_sensor_placement(
    network: str = Query(...),
    scheme_name: str = Query(...),
    sensor_type: str = Query(...),
    method: str = Query(...),
    sensor_count: int = Query(...),
    min_diameter: int = Query(0),
    user_name: str = Query(...),
) -> str:
    if method not in ["sensitivity", "kmeans"]:
        raise HTTPException(
            status_code=400, detail="Invalid method. Must be 'sensitivity' or 'kmeans'"
        )
    if method == "sensitivity":
        pressure_sensor_placement_sensitivity(
            name=network,
            scheme_name=scheme_name,
            sensor_number=sensor_count,
            min_diameter=min_diameter,
            username=user_name,
        )
    elif method == "kmeans":
        pressure_sensor_placement_kmeans(
            name=network,
            scheme_name=scheme_name,
            sensor_number=sensor_count,
            min_diameter=min_diameter,
            username=user_name,
        )
    return "success"


@router.post("/runsimulationmanuallybydate/")
async def fastapi_run_simulation_manually_by_date(
    data: RunSimulationManuallyByDate,
) -> dict[str, str]:
    item = data.dict()
    try:
        simulation.query_corresponding_element_id_and_query_id(item["name"])
        simulation.query_corresponding_pattern_id_and_query_id(item["name"])
        region_result = simulation.query_non_realtime_region(item["name"])
        globals.source_outflow_region_id = simulation.get_source_outflow_region_id(
            item["name"], region_result
        )
        globals.realtime_region_pipe_flow_and_demand_id = (
            simulation.query_realtime_region_pipe_flow_and_demand_id(
                item["name"], region_result
            )
        )
        globals.pipe_flow_region_patterns = simulation.query_pipe_flow_region_patterns(
            item["name"]
        )
        globals.non_realtime_region_patterns = (
            simulation.query_non_realtime_region_patterns(item["name"], region_result)
        )
        (
            globals.source_outflow_region_patterns,
            globals.realtime_region_pipe_flow_and_demand_patterns,
        ) = simulation.get_realtime_region_patterns(
            item["name"],
            globals.source_outflow_region_id,
            globals.realtime_region_pipe_flow_and_demand_id,
        )
        base_date = datetime.strptime(item["simulation_date"], "%Y-%m-%d")
        run_simulation_manually_by_date(
            item["name"], base_date, item["start_time"], item["duration"]
        )
        return {"status": "success"}
    except Exception as exc:
        return {"status": "error", "message": str(exc)}
