from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel, EmailStr
from datetime import datetime, timedelta
import jwt
from passlib.context import CryptContext
from redis import Redis
from rq import Queue
from rq.job import Job
import os
from typing import List, Optional

# --- Configuration ---
SECRET_KEY = os.getenv("SECRET_KEY", "your-super-secret-portfolio-key-change-this")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 1440

# Redis Connection
REDIS_HOST = os.getenv("REDIS_HOST", "redis.geocrop.svc.cluster.local")
redis_conn = Redis(host=REDIS_HOST, port=6379)
task_queue = Queue('geocrop_tasks', connection=redis_conn)

from fastapi.middleware.cors import CORSMiddleware

app = FastAPI(title="GeoCrop API", version="1.1")

# Add CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://portfolio.techarvest.co.zw", "http://localhost:5173"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/login")

# In-memory DB
USERS = {
    "fchinembiri24@gmail.com": {
        "email": "fchinembiri24@gmail.com",
        "hashed_password": "$2b$12$iyR6fFeQAd2CfCDm/CdTSeB8CIjJhAHjA6Et7/UMWm0i0nIAFu21W", 
        "is_active": True,
        "is_admin": True,
        "login_count": 0,
        "login_limit": 9999
    }
}

class UserCreate(BaseModel):
    email: EmailStr
    password: str
    login_limit: int = 3

class UserResponse(BaseModel):
    email: EmailStr
    is_active: bool
    is_admin: bool
    login_count: int
    login_limit: int

class Token(BaseModel):
    access_token: str
    token_type: str
    is_admin: bool

class InferenceJobRequest(BaseModel):
    lat: float
    lon: float
    radius_km: float
    year: str
    model_name: str

def create_access_token(data: dict, expires_delta: timedelta):
    to_encode = data.copy()
    expire = datetime.utcnow() + expires_delta
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

async def get_current_user(token: str = Depends(oauth2_scheme)):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        email: str = payload.get("sub")
        if email is None or email not in USERS:
            raise HTTPException(status_code=401, detail="Invalid credentials")
        return USERS[email]
    except jwt.PyJWTError:
        raise HTTPException(status_code=401, detail="Invalid credentials")

async def get_admin_user(current_user: dict = Depends(get_current_user)):
    if not current_user.get("is_admin"):
        raise HTTPException(status_code=403, detail="Admin privileges required")
    return current_user

@app.post("/auth/login", response_model=Token, tags=["Authentication"])
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    username = form_data.username.strip()
    password = form_data.password.strip()
    
    # Check Admin Bypass
    if username == "fchinembiri24@gmail.com" and password == "P@55w0rd.123":
        user = USERS["fchinembiri24@gmail.com"]
        user["login_count"] += 1
        access_token = create_access_token(
            data={"sub": user["email"]}, 
            expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
        )
        return {"access_token": access_token, "token_type": "bearer", "is_admin": True}

    user = USERS.get(username)
    if not user or not pwd_context.verify(password, user["hashed_password"]):
        raise HTTPException(status_code=401, detail="Incorrect email or password")
    
    if user["login_count"] >= user.get("login_limit", 3):
        raise HTTPException(status_code=403, detail=f"Login limit reached.")

    user["login_count"] += 1
    access_token = create_access_token(
        data={"sub": user["email"]}, 
        expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    )
    return {"access_token": access_token, "token_type": "bearer", "is_admin": user.get("is_admin", False)}

@app.get("/admin/users", response_model=List[UserResponse], tags=["Admin"])
async def list_users(admin: dict = Depends(get_admin_user)):
    return [
        {
            "email": u["email"],
            "is_active": u["is_active"],
            "is_admin": u.get("is_admin", False),
            "login_count": u.get("login_count", 0),
            "login_limit": u.get("login_limit", 3)
        }
        for u in USERS.values()
    ]

@app.post("/admin/users", response_model=UserResponse, tags=["Admin"])
async def create_user(user_in: UserCreate, admin: dict = Depends(get_admin_user)):
    if user_in.email in USERS:
        raise HTTPException(status_code=400, detail="User already exists")
    
    USERS[user_in.email] = {
        "email": user_in.email,
        "hashed_password": pwd_context.hash(user_in.password),
        "is_active": True,
        "is_admin": False,
        "login_count": 0,
        "login_limit": user_in.login_limit
    }
    return {
        "email": user_in.email,
        "is_active": True,
        "is_admin": False,
        "login_count": 0,
        "login_limit": user_in.login_limit
    }

@app.post("/jobs", tags=["Inference"])
async def create_inference_job(job_req: InferenceJobRequest, current_user: dict = Depends(get_current_user)):
    if job_req.radius_km > 5.0:
        raise HTTPException(status_code=400, detail="Radius exceeds 5km limit.")
    
    job = task_queue.enqueue(
        'worker.run_inference', 
        job_req.model_dump(),
        job_timeout='25m' 
    )
    return {"job_id": job.id, "status": "queued"}

@app.get("/jobs/{job_id}", tags=["Inference"])
async def get_job_status(job_id: str, current_user: dict = Depends(get_current_user)):
    try:
        job = Job.fetch(job_id, connection=redis_conn)
    except Exception:
        raise HTTPException(status_code=404, detail="Job not found")

    # Try to get detailed status from custom Redis key
    detailed_status = None
    try:
        status_bytes = redis_conn.get(f"job:{job_id}:status")
        if status_bytes:
            import json
            detailed_status = json.loads(status_bytes.decode('utf-8'))
    except Exception as e:
        print(f"Error fetching detailed status: {e}")

    # Extract ROI from job args
    roi = None
    if job.args and len(job.args) > 0:
        args = job.args[0]
        if isinstance(args, dict):
            roi = {
                "lat": args.get("lat"),
                "lon": args.get("lon"),
                "radius_m": int(float(args.get("radius_km", 0)) * 1000) if "radius_km" in args else args.get("radius_m")
            }

    if job.is_finished:
        result = job.result
        # If detailed status has outputs, prefer those
        if detailed_status and "outputs" in detailed_status:
            result = detailed_status["outputs"]
        
        return {
            "job_id": job.id, 
            "status": "finished", 
            "result": result,
            "detailed": detailed_status,
            "roi": roi
        }
    elif job.is_failed:
        return {
            "job_id": job.id, 
            "status": "failed",
            "error": detailed_status.get("error") if detailed_status else None,
            "roi": roi
        }
    else:
        status = job.get_status()
        # If we have detailed status, use its status/stage/progress
        response = {
            "job_id": job.id,
            "status": status,
            "roi": roi
        }
        if detailed_status:
            response.update({
                "worker_status": detailed_status.get("status"),
                "stage": detailed_status.get("stage"),
                "progress": detailed_status.get("progress"),
                "message": detailed_status.get("message"),
            })
            # Include intermediate outputs (e.g., dw_baseline_url) even if job not finished yet
            if "outputs" in detailed_status:
                response["outputs"] = detailed_status["outputs"]
        return response
