from fastapi import FastAPI, File, Form, UploadFile, HTTPException, Query
from fastapi.middleware.cors import CORSMiddleware
import subprocess
import threading
import time
import os
import json
import uuid
import hashlib
from pathlib import Path
from pydantic import BaseModel
import RPi.GPIO as GPIO

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)

INCOMING_DIR = Path("/tmp/quibot-audio/incoming")
LOCKED_DIR = Path("/tmp/quibot-audio/locked")
PROCESSED_DIR = Path("/tmp/quibot-audio/processed")
INCOMING_DIR.mkdir(parents=True, exist_ok=True)
LOCKED_DIR.mkdir(parents=True, exist_ok=True)
PROCESSED_DIR.mkdir(parents=True, exist_ok=True)

# -------------------------
# GPIO SETUP
# -------------------------
STEP = 23
DIR = 24
EN = 25

GPIO.setmode(GPIO.BCM)
GPIO.setup(STEP, GPIO.OUT)
GPIO.setup(DIR, GPIO.OUT)
GPIO.setup(EN, GPIO.OUT)

GPIO.output(EN, GPIO.LOW)


motor_thread = None

def step_motor(steps, direction, delay=0.001):
    GPIO.output(DIR, direction)

    for _ in range(steps):
        GPIO.output(STEP, GPIO.HIGH)
        time.sleep(delay)
        GPIO.output(STEP, GPIO.LOW)
        time.sleep(delay)

def motor_step(dir):
    dir_pin = GPIO.HIGH if dir == "forward" else GPIO.LOW
    time.sleep(0.02)  # small delay before starting
    print("Motor running...")
    step_motor(200, dir_pin, 0.001)


# -------------------------
# SAFE COMMAND WHITELIST
# -------------------------
COMMANDS = {
    "restart_nginx": ["sudo", "systemctl", "restart", "nginx"],
    "uptime": ["uptime"],
    "update": ["sudo", "apt", "update"]
}


# -------------------------
# API ENDPOINTS
# -------------------------

@app.post("/run")
def run_task(task: str, token: str):
    if token != "MY_SECRET_TOKEN":
        raise HTTPException(status_code=403, detail="Unauthorized")

    if task not in COMMANDS:
        raise HTTPException(status_code=400, detail="Invalid task")

    try:
        result = subprocess.check_output(COMMANDS[task], text=True)
        return {"output": result}
    except subprocess.CalledProcessError as e:
        return {"error": e.output}


@app.post("/motor/step/forward")
def start_motor(token: str):
    global motor_thread

    if token != "MY_SECRET_TOKEN":
        raise HTTPException(status_code=403, detail="Unauthorized")


    motor_thread = threading.Thread(target=motor_step, args=("forward",), daemon=True)
    motor_thread.start()

    return {"status": "motor started"}

@app.post("/motor/step/backwards")
def start_motor(token: str):
    global motor_thread

    if token != "MY_SECRET_TOKEN":
        raise HTTPException(status_code=403, detail="Unauthorized")


    motor_thread = threading.Thread(target=motor_step, args=("backwards",), daemon=True)
    motor_thread.start()

    return {"status": "motor started"}

@app.post("/motor/stop")
def stop_motor(token: str):
    if token != "MY_SECRET_TOKEN":
        raise HTTPException(status_code=403, detail="Unauthorized")

    GPIO.output(EN, GPIO.HIGH)  # disable driver

    return {"status": "motor stopped"}


@app.post("/audio/upload")
async def upload_audio(file: UploadFile = File(...), format: str = "wav"):
    raw_content = await file.read()
    
    checksum = hashlib.sha256(raw_content).hexdigest()[:16]
    filename = f"{checksum[:10]}-{uuid.uuid4().hex[:8]}.wav"
    
    filepath = INCOMING_DIR / filename
    filepath.write_bytes(raw_content)
    
    return {"status": "received", "filename": str(filepath), "lock_url": f"/audio/lock/{filepath.name}"}


@app.get("/audio/incoming")
def list_incoming():
    files = []
    for f in sorted(INCOMING_DIR.iterdir()):
        meta = f.stat()
        files.append({
            "filename": f.name,
            "size_bytes": meta.st_size,
            "modified_iso": time.ctime(meta.st_mtime),
        })
    return {"count": len(files), "files": files}


@app.post("/audio/lock/{filename}")
def lock_audio(filename: str):
    src = INCOMING_DIR / filename
    dst = LOCKED_DIR / filename
    
    if not src.exists():
        raise HTTPException(status_code=404, detail=f"File {filename} not found")
    
    if dst.exists():
        return {"status": "already_locked", "filename": filename}
    
    os.rename(str(src), str(dst))
    return {"status": "locked", "filename": filename}


@app.post("/audio/unlock/{filename}")
def unlock_audio(filename: str):
    src = LOCKED_DIR / filename
    dst = INCOMING_DIR / filename
    
    if not src.exists():
        raise HTTPException(status_code=404, detail=f"File {filename} not found")
    
    os.rename(str(src), str(dst))
    return {"status": "unlocked", "filename": filename}


@app.post("/audio/cancel/{filename}")
def cancel_audio(filename: str):
    src = LOCKED_DIR / filename
    dst = INCOMING_DIR / filename
    
    if not src.exists():
        raise HTTPException(status_code=404, detail=f"File {filename} not found")
    
    os.rename(str(src), str(dst))
    return {"status": "cancelled", "filename": filename}


@app.post("/audio/process/{filename}")
def process_audio(filename: str):
    locked = LOCKED_DIR / filename
    processed = PROCESSED_DIR / filename
    
    if not locked.exists():
        raise HTTPException(status_code=404, detail=f"File {filename} not found")
    
    os.rename(str(locked), str(processed))
    return {"status": "processed", "filename": filename}


@app.on_event("shutdown")
def shutdown():
    global motor_running
    motor_running = False
    GPIO.output(EN, GPIO.HIGH)
    GPIO.cleanup()