import json
from fastapi import APIRouter, Request, HTTPException, Query, Path, Body
from fastapi.responses import PlainTextResponse
from typing import Any, Dict, List
import app.services.project_info as project_info
from app.infra.db.postgresql.database import get_database_instance as get_pg_db
from app.infra.db.timescaledb.database import get_database_instance as get_ts_db
from app.services.tjnetwork import (
    ChangeSet,
    list_project,
    have_project,
    create_project,
    delete_project,
    is_project_open,
    open_project,
    close_project,
    copy_project,
    import_inp,
    export_inp,
    read_inp,
    dump_inp,
    get_all_vertices,
    get_all_scada_elements,
    get_all_district_metering_areas,
    get_all_service_areas,
    get_all_virtual_districts,
    get_extension_data,
    convert_inp_v3_to_v2,
)

# For inp file upload/download
import os
from fastapi import Response, status
from fastapi.responses import FileResponse
inpDir = "data/" # Assuming data directory exists or is defined somewhere. 
# In main.py it was likely global. For safety, let's use a relative path or get from config. 
# But let's stick to what main.py probably used or a default.

router = APIRouter()
lockedPrjs: Dict[str, str] = {}

@router.get("/listprojects/", summary="获取项目列表", description="获取服务器上所有可用的供水管网项目名称列表。")
async def list_projects_endpoint() -> list[str]:
    """
    获取项目列表

    返回所有已创建项目的名称列表。
    """
    return list_project()

@router.get("/haveproject/", summary="检查项目是否存在", description="检查指定名称的项目是否存在。")
async def have_project_endpoint(
    network: str = Query(..., description="管网名称（或数据库名称）")
):
    """
    检查项目是否存在

    - **network**: 管网名称（或数据库名称）
    """
    return have_project(network)

@router.post("/createproject/", summary="创建新项目", description="创建一个新的供水管网项目。如果项目已存在，可能会覆盖或报错（取决于底层实现）。")
async def create_project_endpoint(
    network: str = Query(..., description="管网名称（或数据库名称）")
):
    """
    创建新项目

    - **network**: 管网名称（或数据库名称）
    """
    create_project(network)
    return network

@router.post("/deleteproject/", summary="删除项目", description="永久删除指定的供水管网项目。此操作不可恢复。")
async def delete_project_endpoint(
    network: str = Query(..., description="管网名称（或数据库名称）")
):
    """
    删除项目

    - **network**: 管网名称（或数据库名称）
    """
    delete_project(network)
    return True

@router.get("/isprojectopen/", summary="检查项目是否已打开", description="检查指定项目是否已被加载到内存中。")
async def is_project_open_endpoint(
    network: str = Query(..., description="管网名称（或数据库名称）")
):
    """
    检查项目是否已打开

    - **network**: 管网名称（或数据库名称）
    """
    return is_project_open(network)

@router.post("/openproject/", summary="打开项目", description="将指定项目加载到内存中，并初始化数据库连接池。")
async def open_project_endpoint(
    network: str = Query(..., description="管网名称（或数据库名称）")
):
    """
    打开项目

    - **network**: 管网名称（或数据库名称）
    """
    open_project(network)
    
    # 尝试连接指定数据库
    try:
        # 初始化 PostgreSQL 连接池
        pg_instance = await get_pg_db(network)
        async with pg_instance.get_connection() as conn:
            async with conn.cursor() as cur:
                await cur.execute("SELECT 1")
        
        # 初始化 TimescaleDB 连接池
        ts_instance = await get_ts_db(network)
        async with ts_instance.get_connection() as conn:
            async with conn.cursor() as cur:
                await cur.execute("SELECT 1")
                
    except Exception as e:
        # 记录错误但不阻断项目打开，或者根据需求决定是否阻断
        # 这里选择打印错误，因为 open_project 原本只负责原生部分
        print(f"Failed to connect to databases for {network}: {str(e)}")
        # 如果数据库连接是必须的，可以抛出异常:
        # raise HTTPException(status_code=500, detail=f"Database connection failed: {str(e)}")

    return network

@router.post("/closeproject/", summary="关闭项目", description="将指定项目从内存中卸载，释放资源。")
async def close_project_endpoint(
    network: str = Query(..., description="管网名称（或数据库名称）")
):
    """
    关闭项目

    - **network**: 管网名称（或数据库名称）
    """
    close_project(network)
    return True

@router.post("/copyproject/", summary="复制项目", description="将现有项目复制为新项目。")
async def copy_project_endpoint(
    source: str = Query(..., description="管网名称（或数据库名称）"),
    target: str = Query(..., description="管网名称（或数据库名称）")
):
    """
    复制项目

    - **source**: 管网名称（或数据库名称）
    - **target**: 管网名称（或数据库名称）
    """
    copy_project(source, target)
    return True

@router.post("/importinp/", summary="导入 INP 文件内容", description="将 INP 格式的文本内容导入到指定项目中。")
async def import_inp_endpoint(
    req: Request,
    network: str = Query(..., description="管网名称（或数据库名称）")
):
    """
    导入 INP 文件内容

    - **network**: 管网名称（或数据库名称）
    - **req**: 请求体，需包含 `{"inp": "..."}` 结构
    """
    jo_root = await req.json()
    inp_text = jo_root["inp"]
    ps = {"inp": inp_text}
    ret = import_inp(network, ChangeSet(ps))
    print(ret)
    return ret

@router.get("/exportinp/", response_model=None, summary="导出项目为 ChangeSet", description="导出项目的变更集 (ChangeSet)，包含顶点、SCADA 元素、DMA、SA、VD 等信息。")
async def export_inp_endpoint(
    network: str = Query(..., description="管网名称（或数据库名称）"),
    version: str = Query(..., description="版本号 (通常用于增量更新)")
) -> ChangeSet:
    """
    导出项目为 ChangeSet

    - **network**: 管网名称（或数据库名称）
    - **version**: 版本号
    """
    cs = export_inp(network, version)
    op = cs.operations[0]
    open_project(network)
    op["vertex"] = json.dumps(get_all_vertices(network))
    op["scada"] = json.dumps(get_all_scada_elements(network))
    op["dma"] = json.dumps(get_all_district_metering_areas(network))
    op["sa"] = json.dumps(get_all_service_areas(network))
    op["vd"] = json.dumps(get_all_virtual_districts(network))
    op["legend"] = get_extension_data(network, "legend")

    db = get_extension_data(network, "scada_db")
    print(db)
    scada_db = ""
    if db:
        scada_db = db
    print(scada_db)
    op["scada_db"] = scada_db

    close_project(network)

    return cs

@router.post("/readinp/", summary="读取 INP 文件到项目", description="从服务器文件系统中读取指定的 INP 文件并加载到项目中。")
async def read_inp_endpoint(
    network: str = Query(..., description="管网名称（或数据库名称）"),
    inp: str = Query(..., description="INP 文件名 (不包含路径)")
) -> bool:
    """
    读取 INP 文件到项目

    - **network**: 管网名称（或数据库名称）
    - **inp**: INP 文件名
    """
    read_inp(network, inp)
    return True

@router.get("/dumpinp/", summary="导出项目到 INP 文件", description="将项目当前状态保存为 INP 文件到服务器文件系统。")
async def dump_inp_endpoint(
    network: str = Query(..., description="管网名称（或数据库名称）"),
    inp: str = Query(..., description="目标文件名")
) -> bool:
    """
    导出项目到 INP 文件

    - **network**: 管网名称（或数据库名称）
    - **inp**: 目标文件名
    """
    dump_inp(network, inp)
    return True

@router.get("/isprojectlocked/", summary="检查项目是否被锁定", description="检查指定项目是否处于锁定状态。")
async def is_project_locked_endpoint(
    network: str = Query(..., description="管网名称（或数据库名称）"),
    req: Request = None
):
    """
    检查项目是否被锁定

    - **network**: 管网名称（或数据库名称）
    """
    return network in lockedPrjs.keys()

@router.get("/isprojectlockedbyme/", summary="检查项目是否被当前用户锁定", description="检查指定项目是否被当前客户端 (IP) 锁定。")
async def is_project_locked_by_me_endpoint(
    network: str = Query(..., description="管网名称（或数据库名称）"),
    req: Request = None
):
    """
    检查项目是否被当前用户锁定

    - **network**: 管网名称（或数据库名称）
    """
    client_host = req.client.host
    return lockedPrjs.get(network) == client_host

# 0 successfully locked
# 1 already locked by you
# 2 locked by others
@router.post("/lockproject/", summary="锁定项目", description="锁定指定项目以防止并发修改。")
async def lock_project_endpoint(
    network: str = Query(..., description="管网名称（或数据库名称）"),
    req: Request = None
):
    """
    锁定项目

    返回值:
    - **0**: 锁定成功
    - **1**: 已被当前用户锁定
    - **2**: 已被其他用户锁定
    """
    client_host = req.client.host
    if not network in lockedPrjs.keys():
        lockedPrjs[network] = client_host
        return 0
    else:
        if lockedPrjs.get(network) == client_host:
            return 1
        else:
            return 2

@router.post("/unlockproject/", summary="解锁项目", description="释放对项目的锁定。")
def unlock_project_endpoint(
    network: str = Query(..., description="管网名称（或数据库名称）"),
    req: Request = None
):
    """
    解锁项目

    只有锁定者才能解锁。
    """
    client_host = req.client.host
    if lockedPrjs.get(network) == client_host:
        print("delete key")
        del lockedPrjs[network]
        return True

    return False

# inp file operations
@router.post("/uploadinp/", status_code=status.HTTP_200_OK, summary="上传 INP 文件", description="上传 INP 文件到服务器数据目录。")
async def fastapi_upload_inp(
    afile: bytes = Body(..., description="文件二进制内容"),
    name: str = Query(..., description="保存的文件名")
):
    """
    上传 INP 文件

    - **afile**: 文件内容
    - **name**: 文件名
    """
    if not os.path.exists(inpDir):
        os.makedirs(inpDir, exist_ok=True)
        
    filePath = inpDir + str(name)
    with open(filePath, "wb") as f:
        f.write(afile)
    return True

@router.get("/downloadinp/", status_code=status.HTTP_200_OK, summary="下载 INP 文件", description="从服务器数据目录下载指定的 INP 文件。")
async def fastapi_download_inp(
    name: str = Query(..., description="文件名"),
    response: Response = None
):
    """
    下载 INP 文件

    - **name**: 文件名
    """
    filePath = inpDir + name
    if os.path.exists(filePath):
        return FileResponse(
            filePath, media_type="application/octet-stream", filename="inp.inp"
        )
    else:
        response.status_code = status.HTTP_400_BAD_REQUEST
        return True

# DingZQ, 2024-12-28, convert v3 to v2
@router.get("/convertv3tov2/", response_model=None, summary="转换 INP V3 为 V2", description="将 EPANET 3.0 格式的 INP 内容转换为 2.x 格式。")
async def fastapi_convert_v3_to_v2(
    req: Request
) -> ChangeSet:
    """
    转换 INP V3 为 V2

    - **req**: 请求体，需包含 `{"inp": "..."}` 结构
    """
    network = "v3Tov2"
    jo_root = await req.json()
    inp = jo_root["inp"]
    cs = convert_inp_v3_to_v2(inp)
    op = cs.operations[0]
    open_project(network)
    op["vertex"] = json.dumps(get_all_vertices(network))
    op["scada"] = json.dumps(get_all_scada_elements(network))
    op["dma"] = json.dumps(get_all_district_metering_areas(network))
    op["sa"] = json.dumps(get_all_service_areas(network))
    op["vd"] = json.dumps(get_all_virtual_districts(network))
    op["legend"] = get_extension_data(network, "legend")

    db = get_extension_data(network, "scada_db")
    print(db)
    scada_db = ""
    if db:
        scada_db = db
    print(scada_db)
    op["scada_db"] = scada_db

    close_project(network)

    return cs

@router.post("/readinp/", summary="读取 INP 文件到项目", description="从服务器文件系统中读取指定的 INP 文件并加载到项目中。")
async def read_inp_endpoint(
    network: str = Query(..., description="管网名称（或数据库名称）"),
    inp: str = Query(..., description="INP 文件名 (不包含路径)")
) -> bool:
    """
    读取 INP 文件到项目

    - **network**: 管网名称（或数据库名称）
    - **inp**: INP 文件名
    """
    read_inp(network, inp)
    return True

@router.get("/dumpinp/", summary="导出项目到 INP 文件", description="将项目当前状态保存为 INP 文件到服务器文件系统。")
async def dump_inp_endpoint(
    network: str = Query(..., description="管网名称（或数据库名称）"),
    inp: str = Query(..., description="目标文件名")
) -> bool:
    """
    导出项目到 INP 文件

    - **network**: 管网名称（或数据库名称）
    - **inp**: 目标文件名
    """
    dump_inp(network, inp)
    return True

@router.get("/isprojectlocked/", summary="检查项目是否被锁定", description="检查指定项目是否处于锁定状态。")
async def is_project_locked_endpoint(
    network: str = Query(..., description="管网名称（或数据库名称）"),
    req: Request = None
):
    """
    检查项目是否被锁定

    - **network**: 管网名称（或数据库名称）
    """
    return network in lockedPrjs.keys()

@router.get("/isprojectlockedbyme/", summary="检查项目是否被当前用户锁定", description="检查指定项目是否被当前客户端 (IP) 锁定。")
async def is_project_locked_by_me_endpoint(
    network: str = Query(..., description="管网名称（或数据库名称）"),
    req: Request = None
):
    """
    检查项目是否被当前用户锁定

    - **network**: 管网名称（或数据库名称）
    """
    client_host = req.client.host
    return lockedPrjs.get(network) == client_host

# 0 successfully locked
# 1 already locked by you
# 2 locked by others
@router.post("/lockproject/", summary="锁定项目", description="锁定指定项目以防止并发修改。")
async def lock_project_endpoint(
    network: str = Query(..., description="管网名称（或数据库名称）"),
    req: Request = None
):
    """
    锁定项目

    返回值:
    - **0**: 锁定成功
    - **1**: 已被当前用户锁定
    - **2**: 已被其他用户锁定
    """
    client_host = req.client.host
    if not network in lockedPrjs.keys():
        lockedPrjs[network] = client_host
        return 0
    else:
        if lockedPrjs.get(network) == client_host:
            return 1
        else:
            return 2

@router.post("/unlockproject/", summary="解锁项目", description="释放对项目的锁定。")
def unlock_project_endpoint(
    network: str = Query(..., description="管网名称（或数据库名称）"),
    req: Request = None
):
    """
    解锁项目

    只有锁定者才能解锁。
    """
    client_host = req.client.host
    if lockedPrjs.get(network) == client_host:
        print("delete key")
        del lockedPrjs[network]
        return True

    return False

# inp file operations
@router.post("/uploadinp/", status_code=status.HTTP_200_OK, summary="上传 INP 文件", description="上传 INP 文件到服务器数据目录。")
async def fastapi_upload_inp(
    afile: bytes = Body(..., description="文件二进制内容"),
    name: str = Query(..., description="保存的文件名")
):
    """
    上传 INP 文件

    - **afile**: 文件内容
    - **name**: 文件名
    """
    if not os.path.exists(inpDir):
        os.makedirs(inpDir, exist_ok=True)
        
    filePath = inpDir + str(name)
    with open(filePath, "wb") as f:
        f.write(afile)
    return True

@router.get("/downloadinp/", status_code=status.HTTP_200_OK, summary="下载 INP 文件", description="从服务器数据目录下载指定的 INP 文件。")
async def fastapi_download_inp(
    name: str = Query(..., description="文件名"),
    response: Response = None
):
    """
    下载 INP 文件

    - **name**: 文件名
    """
    filePath = inpDir + name
    if os.path.exists(filePath):
        return FileResponse(
            filePath, media_type="application/octet-stream", filename="inp.inp"
        )
    else:
        response.status_code = status.HTTP_400_BAD_REQUEST
        return True

# DingZQ, 2024-12-28, convert v3 to v2
@router.get("/convertv3tov2/", response_model=None, summary="转换 INP V3 为 V2", description="将 EPANET 3.0 格式的 INP 内容转换为 2.x 格式。")
async def fastapi_convert_v3_to_v2(
    req: Request
) -> ChangeSet:
    """
    转换 INP V3 为 V2

    - **req**: 请求体，需包含 `{"inp": "..."}` 结构
    """
    network = "v3Tov2"
    jo_root = await req.json()
    inp = jo_root["inp"]
    cs = convert_inp_v3_to_v2(inp)
    op = cs.operations[0]
    open_project(network)
    op["vertex"] = json.dumps(get_all_vertices(network))
    op["scada"] = json.dumps(get_all_scada_elements(network))
    op["dma"] = json.dumps(get_all_district_metering_areas(network))
    op["sa"] = json.dumps(get_all_service_areas(network))
    op["vd"] = json.dumps(get_all_virtual_districts(network))
    op["legend"] = get_extension_data(network, "legend")

    db = get_extension_data(network, "scada_db")
    print(db)
    scada_db = ""
    if db:
        scada_db = db
    print(scada_db)
    op["scada_db"] = scada_db

    close_project(network)

    return cs
