import os
import platform
import subprocess
from typing import Optional
from fastapi import FastAPI, Depends, Request, Form, HTTPException, status
from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from sqlalchemy.orm import Session
import database
import auth
import wakeonlan
from pydantic import BaseModel, Field

app = FastAPI(title="Wake on LAN Office Manager", version="1.0.0")

# Setup templates directory
templates = Jinja2Templates(directory="templates")

# Pydantic schemas for API requests
class UserCreate(BaseModel):
    username: str = Field(..., min_length=3, max_length=50)
    password: Optional[str] = Field(None, min_length=6)
    department: str = Field(..., min_length=2, max_length=100)
    is_admin: bool = False

class UserUpdate(BaseModel):
    username: str = Field(..., min_length=3, max_length=50)
    password: Optional[str] = None  # None means do not change password
    department: str = Field(..., min_length=2, max_length=100)
    is_admin: bool = False

class ComputerCreate(BaseModel):
    name: str = Field(..., min_length=2, max_length=100)
    mac_address: str = Field(..., pattern=r"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$")
    ip_address: str = Field(..., min_length=7, max_length=45)
    department: str = Field(..., min_length=2, max_length=100)
    location: Optional[str] = ""
    device_type: str = "desktop"  # desktop, laptop, server, switch

class ComputerUpdate(BaseModel):
    name: str = Field(..., min_length=2, max_length=100)
    mac_address: str = Field(..., pattern=r"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$")
    ip_address: str = Field(..., min_length=7, max_length=45)
    department: str = Field(..., min_length=2, max_length=100)
    location: Optional[str] = ""
    device_type: str = "desktop"

# Ping function helper
def ping_ip(ip_address: str) -> bool:
    system_name = platform.system().lower()
    # On Windows, -n is count, -w is timeout in milliseconds.
    # On Linux/macOS, -c is count, -W is timeout in seconds.
    if system_name == "windows":
        cmd = ["ping", "-n", "1", "-w", "1000", ip_address]
    else:
        cmd = ["ping", "-c", "1", "-W", "1", ip_address]
    
    try:
        res = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        return res.returncode == 0
    except Exception:
        return False

# Database initialization on startup
@app.on_event("startup")
def startup_db():
    database.Base.metadata.create_all(bind=database.engine)
    db = database.SessionLocal()
    try:
        # Seed default admin if it doesn't exist
        admin = db.query(database.User).filter(database.User.username == "admin").first()
        if not admin:
            admin_user = database.User(
                username="admin",
                password_hash=auth.get_password_hash("_Cdevaca:2013"),
                department="Sistemas",
                is_admin=True
            )
            db.add(admin_user)
            db.commit()
    finally:
        db.close()

# Helper middleware-like redirection for 401 exceptions on web pages
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
    # Check if the request expects HTML (e.g. standard page view)
    accept = request.headers.get("accept", "")
    if "text/html" in accept and exc.status_code == status.HTTP_401_UNAUTHORIZED:
        return RedirectResponse(url="/login", status_code=status.HTTP_303_SEE_OTHER)
    
    # Return JSON for standard API calls
    return JSONResponse(
        status_code=exc.status_code,
        content={"detail": exc.detail}
    )

# --- Web Page Routes ---

@app.get("/", response_class=HTMLResponse)
async def root(request: Request, db: Session = Depends(auth.get_db)):
    # Redirect to dashboard if logged in, else to login page
    token = request.cookies.get("access_token")
    if token:
        try:
            auth.get_current_user(request, db)
            return RedirectResponse(url="/dashboard", status_code=status.HTTP_303_SEE_OTHER)
        except HTTPException:
            pass
    return RedirectResponse(url="/login", status_code=status.HTTP_303_SEE_OTHER)

@app.get("/login", response_class=HTMLResponse)
async def login_page(request: Request, error: Optional[str] = None):
    return templates.TemplateResponse(request, "login.html", {"error": error})

@app.post("/login")
async def login(
    request: Request,
    username: str = Form(...),
    password: str = Form(...),
    db: Session = Depends(auth.get_db)
):
    user = db.query(database.User).filter(database.User.username == username).first()
    if not user or not auth.verify_password(password, user.password_hash):
        return templates.TemplateResponse(
            request,
            "login.html",
            {"error": "Usuario o contraseña incorrectos."}
        )
    
    # Create token and store in HttpOnly cookie
    access_token = auth.create_access_token(data={"sub": user.username})
    response = RedirectResponse(url="/dashboard", status_code=status.HTTP_303_SEE_OTHER)
    response.set_cookie(
        key="access_token",
        value=access_token,
        httponly=True,
        max_age=86400, # 1 day
        samesite="lax",
        secure=False  # Set to True in production with HTTPS
    )
    return response

@app.get("/logout")
async def logout():
    response = RedirectResponse(url="/login", status_code=status.HTTP_303_SEE_OTHER)
    response.delete_cookie(key="access_token")
    return response

@app.get("/dashboard", response_class=HTMLResponse)
async def dashboard_page(request: Request, current_user: database.User = Depends(auth.get_current_user), db: Session = Depends(auth.get_db)):
    # Admin sees all, standard users see only their department's computers
    if current_user.is_admin:
        computers = db.query(database.Computer).all()
    else:
        computers = db.query(database.Computer).filter(database.Computer.department == current_user.department).all()
        
    return templates.TemplateResponse(
        request,
        "dashboard.html",
        {"user": current_user, "computers": computers}
    )

@app.get("/admin", response_class=HTMLResponse)
async def admin_page(
    request: Request,
    current_user: database.User = Depends(auth.get_current_admin),
    db: Session = Depends(auth.get_db)
):
    users = db.query(database.User).all()
    computers = db.query(database.Computer).all()
    # List of unique departments to facilitate dropdown selection
    departments = sorted(list(set(filter(None, [u.department for u in users] + [c.department for c in computers]))))
    if not departments:
        departments = ["Administración", "Técnico", "Sistemas", "Ventas"]
        
    return templates.TemplateResponse(
        request,
        "admin.html",
        {"user": current_user, "users": users, "computers": computers, "departments": departments}
    )

# --- Wake on LAN and Ping APIs ---

@app.get("/api/ping/{computer_id}")
async def ping_computer(
    computer_id: int,
    current_user: database.User = Depends(auth.get_current_user),
    db: Session = Depends(auth.get_db)
):
    computer = db.query(database.Computer).filter(database.Computer.id == computer_id).first()
    if not computer:
        raise HTTPException(status_code=404, detail="Equipo no encontrado.")
        
    # Check permissions
    if not current_user.is_admin and computer.department != current_user.department:
        raise HTTPException(status_code=403, detail="No tienes acceso a este equipo.")
        
    is_online = ping_ip(computer.ip_address)
    return {"id": computer_id, "ip": computer.ip_address, "online": is_online}

@app.post("/api/wake/{computer_id}")
async def wake_computer(
    computer_id: int,
    current_user: database.User = Depends(auth.get_current_user),
    db: Session = Depends(auth.get_db)
):
    computer = db.query(database.Computer).filter(database.Computer.id == computer_id).first()
    if not computer:
        raise HTTPException(status_code=404, detail="Equipo no encontrado.")
        
    # Check permissions
    if not current_user.is_admin and computer.department != current_user.department:
        raise HTTPException(status_code=403, detail="No tienes acceso a este equipo.")
        
    try:
        # Send Magic Packet using wakeonlan
        wakeonlan.send_magic_packet(computer.mac_address)
        return {"status": "success", "message": f"Paquete mágico enviado a {computer.name} ({computer.mac_address})"}
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error al enviar WoL: {str(e)}")

# --- Admin API CRUD operations ---

# USER CRUD
@app.post("/api/admin/users", response_model=None)
async def create_user(
    user_data: UserCreate,
    current_user: database.User = Depends(auth.get_current_admin),
    db: Session = Depends(auth.get_db)
):
    # Verify username uniqueness
    existing_user = db.query(database.User).filter(database.User.username == user_data.username).first()
    if existing_user:
        raise HTTPException(status_code=400, detail="El nombre de usuario ya está registrado.")
        
    if not user_data.password:
        raise HTTPException(status_code=400, detail="La contraseña es obligatoria para nuevos usuarios.")
        
    new_user = database.User(
        username=user_data.username,
        password_hash=auth.get_password_hash(user_data.password),
        department=user_data.department,
        is_admin=user_data.is_admin
    )
    db.add(new_user)
    db.commit()
    db.refresh(new_user)
    return {"status": "success", "message": "Usuario creado correctamente.", "user": {"id": new_user.id, "username": new_user.username}}

@app.put("/api/admin/users/{user_id}")
async def update_user(
    user_id: int,
    user_data: UserUpdate,
    current_user: database.User = Depends(auth.get_current_admin),
    db: Session = Depends(auth.get_db)
):
    db_user = db.query(database.User).filter(database.User.id == user_id).first()
    if not db_user:
        raise HTTPException(status_code=404, detail="Usuario no encontrado.")
        
    # Prevent editing username to another user's username
    existing_user = db.query(database.User).filter(database.User.username == user_data.username, database.User.id != user_id).first()
    if existing_user:
        raise HTTPException(status_code=400, detail="El nombre de usuario ya está en uso.")
        
    # Prevent self admin de-escalation for protection
    if db_user.id == current_user.id and not user_data.is_admin:
        raise HTTPException(status_code=400, detail="No puedes quitarte tus propios permisos de administrador.")
        
    db_user.username = user_data.username
    db_user.department = user_data.department
    db_user.is_admin = user_data.is_admin
    
    if user_data.password:
        db_user.password_hash = auth.get_password_hash(user_data.password)
        
    db.commit()
    return {"status": "success", "message": "Usuario actualizado correctamente."}

@app.delete("/api/admin/users/{user_id}")
async def delete_user(
    user_id: int,
    current_user: database.User = Depends(auth.get_current_admin),
    db: Session = Depends(auth.get_db)
):
    db_user = db.query(database.User).filter(database.User.id == user_id).first()
    if not db_user:
        raise HTTPException(status_code=404, detail="Usuario no encontrado.")
        
    # Prevent self deletion
    if db_user.id == current_user.id:
        raise HTTPException(status_code=400, detail="No puedes eliminar tu propio usuario administrador.")
        
    db.delete(db_user)
    db.commit()
    return {"status": "success", "message": "Usuario eliminado correctamente."}

# COMPUTER CRUD
@app.post("/api/admin/computers")
async def create_computer(
    comp_data: ComputerCreate,
    current_user: database.User = Depends(auth.get_current_admin),
    db: Session = Depends(auth.get_db)
):
    new_comp = database.Computer(
        name=comp_data.name,
        mac_address=comp_data.mac_address,
        ip_address=comp_data.ip_address,
        department=comp_data.department,
        location=comp_data.location,
        device_type=comp_data.device_type
    )
    db.add(new_comp)
    db.commit()
    db.refresh(new_comp)
    return {"status": "success", "message": "Equipo añadido correctamente.", "computer": {"id": new_comp.id, "name": new_comp.name}}

@app.put("/api/admin/computers/{computer_id}")
async def update_computer(
    computer_id: int,
    comp_data: ComputerUpdate,
    current_user: database.User = Depends(auth.get_current_admin),
    db: Session = Depends(auth.get_db)
):
    db_comp = db.query(database.Computer).filter(database.Computer.id == computer_id).first()
    if not db_comp:
        raise HTTPException(status_code=404, detail="Equipo no encontrado.")
        
    db_comp.name = comp_data.name
    db_comp.mac_address = comp_data.mac_address
    db_comp.ip_address = comp_data.ip_address
    db_comp.department = comp_data.department
    db_comp.location = comp_data.location
    db_comp.device_type = comp_data.device_type
    
    db.commit()
    return {"status": "success", "message": "Equipo actualizado correctamente."}

@app.delete("/api/admin/computers/{computer_id}")
async def delete_computer(
    computer_id: int,
    current_user: database.User = Depends(auth.get_current_admin),
    db: Session = Depends(auth.get_db)
):
    db_comp = db.query(database.Computer).filter(database.Computer.id == computer_id).first()
    if not db_comp:
        raise HTTPException(status_code=404, detail="Equipo no encontrado.")
        
    db.delete(db_comp)
    db.commit()
    return {"status": "success", "message": "Equipo eliminado correctamente."}
