from datetime import datetime, timedelta
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, Request, Form
from pathlib import Path
import shutil
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel
from sqlmodel import Session, select

from database import settings, get_session
from models import User
import re
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import secrets
import string

def validate_password(password: str):
    if len(password) < 8:
        raise HTTPException(status_code=400, detail="Password must be at least 8 characters long")
    if not re.search(r"[A-Z]", password):
        raise HTTPException(status_code=400, detail="Password must contain at least one uppercase letter")
    if not re.search(r"[a-z]", password):
        raise HTTPException(status_code=400, detail="Password must contain at least one lowercase letter")
    if not re.search(r"\d", password):
        raise HTTPException(status_code=400, detail="Password must contain at least one digit")
    if not re.search(r"[@$!%*?&#]", password):
        raise HTTPException(status_code=400, detail="Password must contain at least one special character (@$!%*?&#)")

def generate_random_password(length=12):
    alphabet = string.ascii_letters + string.digits + "@$!%*?&#"
    while True:
        password = ''.join(secrets.choice(alphabet) for i in range(length))
        if (any(c.islower() for c in password)
                and any(c.isupper() for c in password)
                and any(c.isdigit() for c in password)
                and any(c in "@$!%*?&#" for c in password)):
            return password

router = APIRouter(prefix="/auth", tags=["auth"])

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

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

class TokenData(BaseModel):
    username: Optional[str] = None

class UserSignup(BaseModel):
    username: str
    email: str
    password: str
    full_name: Optional[str] = None

class UserUpdate(BaseModel):
    full_name: Optional[str] = None
    email: Optional[str] = None
    password: Optional[str] = None
    phone: Optional[str] = None

class PasswordChange(BaseModel):
    current_password: str
    new_password: str
    confirm_password: str

class ForgotPasswordRequest(BaseModel):
    email: str

class ResetPasswordRequest(BaseModel):
    token: str
    new_password: str
    confirm_password: str

def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)

def get_password_hash(password):
    return pwd_context.hash(password)

def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=15)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
    return encoded_jwt

async def get_current_user(token: str = Depends(oauth2_scheme), session: Session = Depends(get_session)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
        token_data = TokenData(username=username)
    except JWTError:
        raise credentials_exception
    user = session.exec(select(User).where(User.username == token_data.username)).first()
    if user is None:
        raise credentials_exception
    return user

@router.post("/signup", response_model=Token)
async def signup(user_data: UserSignup, session: Session = Depends(get_session)):
    # Check if user already exists
    existing_user = session.exec(select(User).where((User.username == user_data.username) | (User.email == user_data.email))).first()
    if existing_user:
        raise HTTPException(status_code=400, detail="Username or email already registered")
    
    validate_password(user_data.password)
    
    hashed_pwd = get_password_hash(user_data.password)
    user = User(
        username=user_data.username,
        email=user_data.email,
        hashed_password=hashed_pwd,
        full_name=user_data.full_name
    )
    session.add(user)
    session.commit()
    session.refresh(user)
    
    access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user.username}, expires_delta=access_token_expires
    )
    return {"access_token": access_token, "token_type": "bearer"}

@router.post("/login", response_model=Token)
async def login(form_data: OAuth2PasswordRequestForm = Depends(), session: Session = Depends(get_session)):
    # Check if the identifier is a username OR an email
    user = session.exec(
        select(User).where(
            (User.username == form_data.username) | (User.email == form_data.username)
        )
    ).first()
    
    if not user or not verify_password(form_data.password, user.hashed_password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect identifier or password",
            headers={"WWW-Authenticate": "Bearer"},
        )
    
    access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user.username}, expires_delta=access_token_expires
    )
    return {"access_token": access_token, "token_type": "bearer", "is_admin": user.is_admin}

@router.get("/me")
async def read_users_me(current_user: User = Depends(get_current_user)):
    return current_user

@router.put("/me")
async def update_user(user_update: UserUpdate, current_user: User = Depends(get_current_user), session: Session = Depends(get_session)):
    if user_update.full_name:
        current_user.full_name = user_update.full_name
    if user_update.email:
        current_user.email = user_update.email
    if user_update.phone:
        current_user.phone = user_update.phone
    if user_update.password:
        validate_password(user_update.password)
        current_user.hashed_password = get_password_hash(user_update.password)
    
    session.add(current_user)
    session.commit()
    session.refresh(current_user)
    return current_user

@router.post("/change-password")
async def change_password_endpoint(
    pwd: PasswordChange, 
    current_user: User = Depends(get_current_user), 
    session: Session = Depends(get_session)
):
    if not verify_password(pwd.current_password, current_user.hashed_password):
        raise HTTPException(status_code=400, detail="Incorrect current password")
    if pwd.new_password != pwd.confirm_password:
        raise HTTPException(status_code=400, detail="Passwords do not match")
    validate_password(pwd.new_password)
    current_user.hashed_password = get_password_hash(pwd.new_password)
    session.add(current_user)
    session.commit()
    return {"message": "Password updated successfully"}

@router.post("/avatar")
async def upload_avatar(
    file: UploadFile = File(...), 
    current_user: User = Depends(get_current_user),
    session: Session = Depends(get_session)
):
    upload_dir = Path("uploads/avatars")
    upload_dir.mkdir(parents=True, exist_ok=True)
    
    # Validation: Only JPEG or PDF
    ext = Path(file.filename).suffix.lower()
    if ext not in [".jpg", ".jpeg", ".pdf"]:
        raise HTTPException(status_code=400, detail="Only JPEG (.jpg, .jpeg) or PDF (.pdf) formats are allowed for profile photos.")

    # Sanitize filename or use ID
    safe_name = "".join([c for c in file.filename if c.isalpha() or c.isdigit() or c in "._-"])
    filename = f"user_{current_user.id}_{safe_name}"
    dest_path = upload_dir / filename
    
    with open(dest_path, "wb") as buffer:
        shutil.copyfileobj(file.file, buffer)
    
    current_user.profile_photo = f"/uploads/avatars/{filename}"
    session.add(current_user)
    session.commit()
    session.refresh(current_user)
    return {"photo_url": current_user.profile_photo}

@router.post("/forgot-password")
async def forgot_password(req_data: ForgotPasswordRequest, request: Request, session: Session = Depends(get_session)):
    user = session.exec(select(User).where(User.email == req_data.email)).first()
    if not user:
        # Don't reveal if user exists
        return {"message": "If this email is registered, you will receive a reset link."}
    
    # Generate reset token (short lived)
    expires = timedelta(minutes=15)
    token = create_access_token(data={"sub": user.username, "type": "reset"}, expires_delta=expires)
    
    # Send Email
    base_url = str(request.base_url).rstrip('/')
    reset_link = f"{base_url}/login?reset_token={token}"
    
    if not settings.SMTP_USER or not settings.SMTP_PASSWORD:
        print(f"DEBUG: SMTP not configured. Reset Link for {user.email}: {reset_link}")
        return {"message": "Reset link generated (Dev Mode). Check console."}
        
    try:
        msg = MIMEMultipart()
        msg['From'] = settings.SMTP_USER
        msg['To'] = user.email
        msg['Subject'] = "Password Reset Request - Resume Parser"
        
        body = f"Hello,\n\nYou requested a password reset. Click the link below to set a new password:\n\n{reset_link}\n\nThis link will expire in 15 minutes.\n\nIf you did not request this, please ignore this email."
        msg.attach(MIMEText(body, 'plain'))
        
        if settings.SMTP_PORT == 465:
            server = smtplib.SMTP_SSL(settings.SMTP_SERVER, settings.SMTP_PORT)
        else:
            server = smtplib.SMTP(settings.SMTP_SERVER, settings.SMTP_PORT)
            server.starttls()
            
        server.login(settings.SMTP_USER, settings.SMTP_PASSWORD)
        text = msg.as_string()
        server.sendmail(settings.SMTP_USER, user.email, text)
        server.quit()
    except Exception as e:
        print(f"Failed to send email: {e}")
        return {"message": "Failed to send reset email. Please try again later.", "status": "error"}
        
    return {"message": "If your email is registered, you will receive a reset link shortly."}

@router.post("/reset-password")
async def reset_password_endpoint(req: ResetPasswordRequest, session: Session = Depends(get_session)):
    if req.new_password != req.confirm_password:
        raise HTTPException(status_code=400, detail="Passwords do not match")
        
    try:
        payload = jwt.decode(req.token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
        username: str = payload.get("sub")
        token_type: str = payload.get("type")
        
        if username is None or token_type != "reset":
            raise HTTPException(status_code=400, detail="Invalid token")
            
    except JWTError:
        raise HTTPException(status_code=400, detail="Invalid or expired token")
        
    user = session.exec(select(User).where(User.username == username)).first()
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
        
    validate_password(req.new_password)
    user.hashed_password = get_password_hash(req.new_password)
    session.add(user)
    session.commit()
    
    return {"message": "Password reset successfully. You can now login."}


@router.get("/dashboard/users")
async def list_users(
    session: Session = Depends(get_session),
    current_user: User = Depends(get_current_user)
):
    # Only admins can list users
    if not current_user.is_admin:
        raise HTTPException(status_code=403, detail="Not authorized. Admin access required.")
        
    users = session.exec(select(User)).all()
    # return users without hashed_password
    results = []
    for u in users:
        results.append({
            "id": u.id,
            "username": u.username,
            "email": u.email,
            "full_name": u.full_name,
            "is_active": u.is_active,
            "is_admin": u.is_admin,
            "created_at": u.created_at
        })
    return results

@router.post("/dashboard/users")
async def add_user_from_dashboard(
    payload: dict,
    session: Session = Depends(get_session),
    current_user: User = Depends(get_current_user)
):
    # Only admins can add users
    if not current_user.is_admin:
        raise HTTPException(status_code=403, detail="Not authorized. Admin access required.")

    email = payload.get("email")
    full_name = payload.get("full_name")
    is_admin = payload.get("is_admin", False)
    
    if not email or not full_name:
        raise HTTPException(status_code=400, detail="Name and Email are required")

    # Use email as username
    username = email

    # Check existing user
    existing_user = session.exec(
        select(User).where(
            (User.username == username) |
            (User.email == email)
        )
    ).first()

    if existing_user:
        raise HTTPException(status_code=400, detail="User with this email already exists")

    # Auto-generate password
    password = generate_random_password()
    hashed_password = get_password_hash(password)

    user = User(
        username=username,
        email=email,
        full_name=full_name,
        hashed_password=hashed_password,
        is_active=True,
        is_admin=is_admin
    )

    session.add(user)
    session.commit()
    session.refresh(user)

    # Send Email
    if settings.SMTP_USER and settings.SMTP_PASSWORD:
        try:
            msg = MIMEMultipart()
            msg['From'] = settings.SMTP_USER
            msg['To'] = email
            msg['Subject'] = "Your Account Credentials - Resume Parser"
            
            body = f"""
            Hello {full_name},

            An account has been created for you on the Resume Parser Dashboard.
            
            Your login credentials are:
            Email/Username: {email}
            Password: {password}

            You can login at: http://localhost:8000/login

            Please change your password after logging in for security.
            """
            msg.attach(MIMEText(body, 'plain'))
            
            # Note: Using SMTP_PORT from settings. 
            # If 465 use SMTP_SSL, if 587 use SMTP + starttls
            if settings.SMTP_PORT == 465:
                server = smtplib.SMTP_SSL(settings.SMTP_SERVER, settings.SMTP_PORT)
            else:
                server = smtplib.SMTP(settings.SMTP_SERVER, settings.SMTP_PORT)
                server.starttls()
            
            server.login(settings.SMTP_USER, settings.SMTP_PASSWORD)
            text = msg.as_string()
            server.sendmail(settings.SMTP_USER, email, text)
            server.quit()
        except Exception as e:
            print(f"Failed to send email: {e}")
            # We still created the user, but inform about email failure
            return {"message": "User created, but failed to send credentials email.", "password": password}
    else:
        print(f"DEBUG: SMTP not configured. Password for {email}: {password}")
        return {"message": "User created (Dev Mode). SMTP not configured.", "password": password}

    return {"message": "User created successfully and credentials sent to email."}
