import os
import smtplib
import random
import uuid
import shutil
import requests
import asyncio
from email.message import EmailMessage
from datetime import datetime, timedelta, timezone
from typing import List, Optional

from fastapi import FastAPI, Depends, HTTPException, status, UploadFile, File, Form
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from sqlalchemy import create_engine, Column, String, Integer, Boolean, DateTime, ForeignKey, Text
from sqlalchemy.dialects.postgresql import UUID as pg_UUID
from sqlalchemy.orm import sessionmaker, Session, declarative_base, relationship
from pydantic import BaseModel, EmailStr

from config import settings
import call_engine
from rag_manager import process_and_store_document

UPLOAD_DIR = "uploads"
RECORDING_DIR = os.path.join(UPLOAD_DIR, "recordings")
os.makedirs(UPLOAD_DIR, exist_ok=True)
os.makedirs(RECORDING_DIR, exist_ok=True)

engine = create_engine(settings.DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

# ORM Models 
class User(Base):
    __tablename__ = "users"
    id = Column(pg_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    first_name = Column(String(100), nullable=False)
    last_name = Column(String(100), nullable=False)
    employee_id = Column(String(50), unique=True, nullable=False)
    department = Column(String(100))
    designation = Column(String(100))
    email = Column(String(255), unique=True, nullable=False)
    mobile = Column(String(20))
    role = Column(String(20), default="User", nullable=False)
    password_hash = Column(String(255))
    is_active = Column(Boolean, default=True)
    last_login = Column(DateTime(timezone=True))

class AuthToken(Base):
    __tablename__ = "auth_tokens"
    id = Column(pg_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    user_id = Column(pg_UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"))
    otp_code = Column(String(10), nullable=False)
    expires_at = Column(DateTime(timezone=True), nullable=False)
    is_used = Column(Boolean, default=False)

class Project(Base):
    __tablename__ = "projects"
    id = Column(pg_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    name = Column(String(255), unique=True, nullable=False)
    test_duration_seconds = Column(Integer, default=180, nullable=False)
    target_rating = Column(Integer, default=10, nullable=False)
    voice_id = Column(String(100)) 
    created_by = Column(pg_UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"))

class ProjectDocument(Base):
    __tablename__ = "project_documents"
    id = Column(pg_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    project_id = Column(pg_UUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE"))
    file_name = Column(String(255), nullable=False)
    file_type = Column(String(10))
    file_path = Column(Text, nullable=False)
    uploaded_by = Column(pg_UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"))

class UserProjectAssignment(Base):
    __tablename__ = "user_project_assignments"
    user_id = Column(pg_UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), primary_key=True)
    project_id = Column(pg_UUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE"), primary_key=True)
    assigned_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))

class TestReport(Base):
    __tablename__ = "test_reports"
    id = Column(pg_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    user_id = Column(pg_UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"))
    project_id = Column(pg_UUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE"))
    test_date_time = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
    duration_seconds = Column(Integer, nullable=False)
    rating = Column(Integer)
    ai_statement_short = Column(String(50))
    ai_statement_full = Column(Text)
    recording_path = Column(Text)
    created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))

# Pydantic Schemas
class UserCreate(BaseModel):
    first_name: str
    last_name: str
    employee_id: str
    department: Optional[str] = None
    designation: Optional[str] = None
    email: EmailStr
    mobile: Optional[str] = None
    role: str = "User"

class UserResponse(UserCreate):
    id: uuid.UUID
    is_active: bool
    class Config:
        from_attributes = True

class OTPRequest(BaseModel):
    email: EmailStr

class OTPVerify(BaseModel):
    email: EmailStr
    otp_code: str

class ProjectCreate(BaseModel):
    name: str
    test_duration_seconds: int = 180
    target_rating: int = 10
    voice_id: Optional[str] = None
    created_by: Optional[uuid.UUID] = None 

class ProjectUpdate(BaseModel):
    test_duration_seconds: Optional[int] = None
    target_rating: Optional[int] = None
    voice_id: Optional[str] = None

class ProjectResponse(ProjectCreate):
    id: uuid.UUID
    class Config:
        from_attributes = True

class ProjectAssignRequest(BaseModel):
    user_id: str
    project_id: str

class ReportResponse(BaseModel):
    id: uuid.UUID
    user_id: uuid.UUID
    project_id: uuid.UUID
    test_date_time: datetime
    duration_seconds: int
    rating: int
    ai_statement_short: str
    ai_statement_full: str
    recording_path: Optional[str] = None
    class Config:
        from_attributes = True

def send_email_otp(to_email: str, otp_code: str):
    msg = EmailMessage()
    msg.set_content(f"Your login OTP for the AI Agent Platform is: {otp_code}\n\nThis code expires in 10 minutes.")
    msg['Subject'] = 'Your AI Agent Login OTP'
    msg['From'] = settings.SMTP_FROM
    msg['To'] = to_email
    try:
        with smtplib.SMTP_SSL(settings.SMTP_HOST, settings.SMTP_PORT) as server:
            server.login(settings.SMTP_USER, settings.SMTP_PASS)
            server.send_message(msg)
    except Exception as e:
        pass

app = FastAPI(title="AI Customer Agent API")

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

app.mount("/uploads", StaticFiles(directory="uploads"), name="uploads")
app.include_router(call_engine.router)

@app.get("/")
def read_root():
    return {"status": "AI Customer Agent API is running"}

@app.get("/voices/")
def get_available_voices():
    try:
        headers = {"xi-api-key": settings.ELEVENLABS_API_KEY}
        response = requests.get("https://api.elevenlabs.io/v1/voices", headers=headers)
        if response.status_code == 200:
            data = response.json()
            voices = [{"voice_id": v["voice_id"], "name": v["name"]} for v in data.get("voices", [])]
            return {"voices": voices}
        return {"voices": []}
    except:
        return {"voices": []}

@app.post("/projects/", response_model=ProjectResponse, status_code=status.HTTP_201_CREATED)
def create_project(project: ProjectCreate, db: Session = Depends(get_db)):
    db_project = db.query(Project).filter(Project.name == project.name).first()
    if db_project:
        raise HTTPException(status_code=400, detail="Project name already exists")
    new_project = Project(**project.dict())
    db.add(new_project)
    db.commit()
    db.refresh(new_project)
    return new_project

@app.get("/projects/", response_model=List[ProjectResponse])
def get_projects(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
    return db.query(Project).offset(skip).limit(limit).all()

@app.put("/projects/{project_id}", response_model=ProjectResponse)
def update_project(project_id: uuid.UUID, proj_update: ProjectUpdate, db: Session = Depends(get_db)):
    db_project = db.query(Project).filter(Project.id == project_id).first()
    if not db_project:
        raise HTTPException(status_code=404, detail="Project not found")
    
    if proj_update.test_duration_seconds is not None: db_project.test_duration_seconds = proj_update.test_duration_seconds
    if proj_update.target_rating is not None: db_project.target_rating = proj_update.target_rating
    if proj_update.voice_id is not None: db_project.voice_id = proj_update.voice_id
        
    db.commit()
    db.refresh(db_project)
    return db_project

@app.post("/projects/assign")
def assign_project_to_user(req: ProjectAssignRequest, db: Session = Depends(get_db)):
    existing = db.query(UserProjectAssignment).filter_by(user_id=req.user_id, project_id=req.project_id).first()
    if existing: raise HTTPException(status_code=400, detail="Project already assigned")
    assignment = UserProjectAssignment(user_id=req.user_id, project_id=req.project_id)
    db.add(assignment)
    db.commit()
    return {"message": "Project assigned successfully"}

@app.get("/users/{user_id}/projects", response_model=List[ProjectResponse])
def get_user_projects(user_id: str, db: Session = Depends(get_db)):
    user = db.query(User).filter(User.id == user_id).first()
    if not user: raise HTTPException(status_code=404, detail="User not found")
    if user.role == "Admin": return db.query(Project).all()
    projects = db.query(Project).join(UserProjectAssignment).filter(UserProjectAssignment.user_id == user_id).all()
    return projects

@app.post("/projects/{project_id}/documents/")
async def upload_project_document(project_id: str, file: UploadFile = File(...), uploaded_by: str = Form(None), db: Session = Depends(get_db)):
    ext = file.filename.split('.')[-1].lower()
    if ext not in ['pdf', 'docx', 'txt']: raise HTTPException(status_code=400, detail="Only PDF, DOCX, TXT supported.")
    file_path = os.path.join(UPLOAD_DIR, f"{uuid.uuid4()}_{file.filename}")
    with open(file_path, "wb") as buffer:
        shutil.copyfileobj(file.file, buffer)
    new_doc = ProjectDocument(project_id=project_id, file_name=file.filename, file_type=ext, file_path=file_path, uploaded_by=uploaded_by)
    db.add(new_doc)
    db.commit()
    process_and_store_document(project_id=str(project_id), document_id=str(new_doc.id), file_path=file_path, file_type=ext)
    return {"message": "Document uploaded"}

@app.post("/users/", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
def create_user(user: UserCreate, db: Session = Depends(get_db)):
    db_user = db.query(User).filter(User.email == user.email).first()
    if db_user: raise HTTPException(status_code=400, detail="Email already registered")
    new_user = User(**user.dict())
    db.add(new_user)
    db.commit()
    db.refresh(new_user)
    return new_user

@app.get("/users/", response_model=List[UserResponse])
def get_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
    return db.query(User).offset(skip).limit(limit).all()


# --- INCREASED TIMEOUT: Now waits up to 45 seconds for OpenAI to finish ---
@app.post("/reports/upload-recording/")
async def upload_recording(
    user_id: str = Form(...), 
    project_id: str = Form(...), 
    file: UploadFile = File(...), 
    db: Session = Depends(get_db)
):
    file_name = f"{uuid.uuid4()}_{file.filename}"
    file_path = os.path.join(RECORDING_DIR, file_name)
    with open(file_path, "wb") as buffer:
        shutil.copyfileobj(file.file, buffer)
    
    for _ in range(45):
        db.commit() 
        latest_report = db.query(TestReport).filter_by(user_id=user_id, project_id=project_id).order_by(TestReport.created_at.desc()).first()
        
        if latest_report and latest_report.recording_path is None:
            time_diff = datetime.now(timezone.utc) - latest_report.created_at
            if time_diff.total_seconds() < 120:
                latest_report.recording_path = file_path
                db.commit()
                return {"message": "Recording successfully linked to the new report", "path": file_path}
                
        await asyncio.sleep(1) 
        
    return {"message": "Recording saved but evaluation timed out", "path": file_path}


@app.get("/reports/", response_model=List[ReportResponse])
def get_reports(user_id: Optional[str] = None, project_id: Optional[str] = None, db: Session = Depends(get_db)):
    query = db.query(TestReport)
    if user_id: query = query.filter(TestReport.user_id == user_id)
    if project_id: query = query.filter(TestReport.project_id == project_id)
    return query.order_by(TestReport.test_date_time.desc()).all()

@app.post("/auth/request-otp")
def request_otp(req: OTPRequest, db: Session = Depends(get_db)):
    user = db.query(User).filter(User.email == req.email).first()
    if not user: raise HTTPException(status_code=404, detail="User not found")
    otp_code = str(random.randint(100000, 999999))
    expires_at = datetime.now(timezone.utc) + timedelta(minutes=10)
    db.query(AuthToken).filter(AuthToken.user_id == user.id, AuthToken.is_used == False).update({"is_used": True})
    new_token = AuthToken(user_id=user.id, otp_code=otp_code, expires_at=expires_at)
    db.add(new_token)
    db.commit()
    send_email_otp(user.email, otp_code)
    return {"message": "OTP sent successfully"}

@app.post("/auth/verify-otp")
def verify_otp(req: OTPVerify, db: Session = Depends(get_db)):
    user = db.query(User).filter(User.email == req.email).first()
    if not user: raise HTTPException(status_code=404, detail="User not found")
    now = datetime.now(timezone.utc)
    token = db.query(AuthToken).filter(AuthToken.user_id == user.id, AuthToken.otp_code == req.otp_code, AuthToken.is_used == False, AuthToken.expires_at > now).first()
    if not token: raise HTTPException(status_code=400, detail="Invalid or expired OTP")
    token.is_used = True
    user.last_login = now
    db.commit()
    return {"message": "Login successful", "user_id": str(user.id), "role": user.role}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8010)