from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse
from pydantic import BaseModel
from typing import Optional, List
import json
import asyncio
import os

from arcros.database import DatabaseManager
from arcros import create_publisher, create_subscriber

app = FastAPI(title="arcros Web Dashboard")

script_dir = os.path.dirname(__file__)
static_dir = os.path.join(script_dir, "static")
template_dir = os.path.join(script_dir, "templates")

app.mount("/static", StaticFiles(directory=static_dir), name="static")

db = DatabaseManager()

active_subscribers = {}


class PublisherCreate(BaseModel):
    name: str
    port: Optional[int] = None
    description: str = ""


class PublisherUpdate(BaseModel):
    description: Optional[str] = None


class MessagePublish(BaseModel):
    data: dict


class ConfigUpdate(BaseModel):
    start: int
    count: int


@app.get("/", response_class=HTMLResponse)
async def read_root():
    with open(os.path.join(template_dir, "index.html"), "r") as f:
        return HTMLResponse(content=f.read())


@app.get("/api/publishers")
async def get_publishers():
    publishers = db.list_all_publishers()
    return {"publishers": publishers}


@app.get("/api/publishers/{name}")
async def get_publisher(name: str):
    pub = db.get_publisher(name)
    if not pub:
        raise HTTPException(status_code=404, detail="Publisher not found")
    return pub


@app.post("/api/publishers")
async def create_publisher_endpoint(pub: PublisherCreate):
    try:
        if db.publisher_exists(pub.name):
            raise HTTPException(status_code=400, detail="Publisher already exists")
        
        port = pub.port or db.find_available_port()
        if port is None:
            raise HTTPException(status_code=500, detail="No available ports")
        
        db.add_publisher(pub.name, port, 'localhost', pub.description)
        return {"message": "Publisher created", "name": pub.name, "port": port}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.delete("/api/publishers/{name}")
async def delete_publisher(name: str):
    if db.remove_publisher(name):
        return {"message": "Publisher deleted"}
    raise HTTPException(status_code=404, detail="Publisher not found")


@app.get("/api/subscriptions")
async def get_subscriptions():
    subs = db.get_all_subscriptions()
    return {"subscriptions": subs}


@app.get("/api/graph")
async def get_graph():
    graph = db.get_graph()
    return {"graph": graph}


@app.get("/api/config")
async def get_config():
    start, end = db.get_port_range()
    used_ports = db.get_used_ports()
    return {
        "start_port": start,
        "end_port": end - 1,
        "port_count": end - start,
        "used_ports": len(used_ports),
        "available_ports": (end - start) - len(used_ports)
    }


@app.post("/api/config")
async def update_config(config: ConfigUpdate):
    if config.count < 10:
        raise HTTPException(status_code=400, detail="Port count must be at least 10")
    if config.count > 10000:
        raise HTTPException(status_code=400, detail="Port count cannot exceed 10000")
    
    db.set_port_range(config.start, config.count)
    return {"message": "Config updated", "start": config.start, "count": config.count}


@app.get("/api/status")
async def get_status():
    import zmq
    publishers = db.list_all_publishers()
    status_list = []
    
    context = zmq.Context()
    
    for pub_info in publishers:
        name = pub_info['name']
        port = pub_info['port']
        host = pub_info['host']
        
        socket = context.socket(zmq.SUB)
        socket.setsockopt(zmq.SUBSCRIBE, b'')
        socket.setsockopt(zmq.RCVTIMEO, 500)
        
        is_active = False
        data_types = None
        
        try:
            socket.connect(f"tcp://{host}:{port}")
            data = socket.recv_json()
            is_active = True
            
            if isinstance(data, dict):
                data_types = {k: type(v).__name__ for k, v in data.items()}
        except:
            pass
        finally:
            socket.close()
        
        status_list.append({
            "name": name,
            "port": port,
            "host": host,
            "active": is_active,
            "data_types": data_types,
            "description": pub_info.get('description', '')
        })
    
    context.term()
    return {"publishers": status_list}


@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    try:
        while True:
            status = await get_status()
            graph = await get_graph()
            
            await websocket.send_json({
                "type": "update",
                "status": status,
                "graph": graph
            })
            
            await asyncio.sleep(2)
    except WebSocketDisconnect:
        pass


def run_server(host: str = "0.0.0.0", port: int = 8000):
    import uvicorn
    uvicorn.run(app, host=host, port=port)


if __name__ == "__main__":
    run_server()
