from fastapi import FastAPI, APIRouter, HTTPException, Depends, Request
from dotenv import load_dotenv
from starlette.middleware.cors import CORSMiddleware
from motor.motor_asyncio import AsyncIOMotorClient
import os
import logging
import httpx
from pathlib import Path
from pydantic import BaseModel, Field
from typing import List, Optional
import uuid
from datetime import datetime, timezone, timedelta
import bcrypt
import jwt
import secrets

ROOT_DIR = Path(__file__).parent
load_dotenv(ROOT_DIR / '.env')

# MongoDB connection
mongo_url = os.environ['MONGO_URL']
client = AsyncIOMotorClient(mongo_url)
db = client[os.environ.get('DB_NAME', 'opel_attendance')]

# JWT config
JWT_SECRET = os.environ.get('JWT_SECRET', secrets.token_hex(32))
JWT_ALGORITHM = "HS256"
JWT_EXPIRY_HOURS = 24

# Google Sheets Web App URL
GOOGLE_SHEETS_URL = os.environ.get('GOOGLE_SHEETS_URL', 'https://script.google.com/macros/s/AKfycbz-ghWEo8RAlnErQZJt4X44CdlXIk7SlBYTdFNIf_w6A_ubkY0m1Zcejgy3F0B7HEKaAg/exec')

# IST timezone
IST = timezone(timedelta(hours=5, minutes=30))

app = FastAPI()
api_router = APIRouter(prefix="/api")

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

# --- Models ---
class EmployeeCreate(BaseModel):
    employee_id: str
    name: str
    password: str
    role: str = "employee"

class EmployeeUpdate(BaseModel):
    name: Optional[str] = None
    email: Optional[str] = None
    mobile: Optional[str] = None
    role: Optional[str] = None
    is_active: Optional[bool] = None

class LoginRequest(BaseModel):
    employee_id: str
    password: str

class RegisterRequest(BaseModel):
    name: str
    email: str
    mobile: str
    employee_id: str
    password: str

class AttendanceRequest(BaseModel):
    latitude: float
    longitude: float

class ForgotPasswordRequest(BaseModel):
    employee_id: str

class ResetPasswordRequest(BaseModel):
    employee_id: str
    reset_token: str
    new_password: str

class ChangePasswordRequest(BaseModel):
    employee_id: str
    new_password: str

# --- Helpers ---
def hash_password(password: str) -> str:
    return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')

def verify_password(password: str, hashed: str) -> bool:
    return bcrypt.checkpw(password.encode('utf-8'), hashed.encode('utf-8'))

def create_token(employee_id: str, role: str) -> str:
    payload = {
        "employee_id": employee_id,
        "role": role,
        "exp": datetime.now(timezone.utc) + timedelta(hours=JWT_EXPIRY_HOURS)
    }
    return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)

def decode_token(token: str) -> dict:
    try:
        return jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Token expired")
    except jwt.InvalidTokenError:
        raise HTTPException(status_code=401, detail="Invalid token")

async def get_current_user(request: Request) -> dict:
    auth = request.headers.get("Authorization", "")
    if not auth.startswith("Bearer "):
        raise HTTPException(status_code=401, detail="Missing token")
    token_data = decode_token(auth[7:])
    emp = await db.employees.find_one({"employee_id": token_data["employee_id"]}, {"_id": 0})
    if not emp:
        raise HTTPException(status_code=401, detail="User not found")
    return emp

async def require_admin(request: Request) -> dict:
    user = await get_current_user(request)
    if user.get("role") != "admin":
        raise HTTPException(status_code=403, detail="Admin access required")
    return user

def get_ist_now() -> datetime:
    return datetime.now(IST)

def get_ist_today_str() -> str:
    return get_ist_now().strftime("%Y-%m-%d")

async def push_to_google_sheets(data: dict):
    """Push attendance record to Google Sheets"""
    try:
        async with httpx.AsyncClient(timeout=15.0) as client_http:
            response = await client_http.post(GOOGLE_SHEETS_URL, json=data, follow_redirects=True)
            logger.info(f"Google Sheets push: {response.status_code}")
            return True
    except Exception as e:
        logger.error(f"Google Sheets push failed: {e}")
        return False

# --- Seed Data ---
@app.on_event("startup")
async def seed_data():
    admin = await db.employees.find_one({"employee_id": "ADMIN001"})
    if not admin:
        employees = [
            {"id": str(uuid.uuid4()), "employee_id": "ADMIN001", "name": "Admin User", "email": "admin@opellifts.com", "mobile": "9999999999", "password_hash": hash_password("admin123"), "role": "admin", "is_active": True, "created_at": datetime.now(timezone.utc).isoformat()},
            {"id": str(uuid.uuid4()), "employee_id": "EMP001", "name": "Rajesh Kumar", "email": "rajesh@opellifts.com", "mobile": "9876543210", "password_hash": hash_password("password123"), "role": "employee", "is_active": True, "created_at": datetime.now(timezone.utc).isoformat()},
            {"id": str(uuid.uuid4()), "employee_id": "EMP002", "name": "Priya Sharma", "email": "priya@opellifts.com", "mobile": "9876543211", "password_hash": hash_password("password123"), "role": "employee", "is_active": True, "created_at": datetime.now(timezone.utc).isoformat()},
            {"id": str(uuid.uuid4()), "employee_id": "EMP003", "name": "Amit Patel", "email": "amit@opellifts.com", "mobile": "9876543212", "password_hash": hash_password("password123"), "role": "employee", "is_active": True, "created_at": datetime.now(timezone.utc).isoformat()},
        ]
        await db.employees.insert_many(employees)
        logger.info("Seeded default employees")

# --- Auth Endpoints ---
@api_router.post("/auth/login")
async def login(req: LoginRequest):
    emp = await db.employees.find_one({"employee_id": req.employee_id}, {"_id": 0})
    if not emp:
        raise HTTPException(status_code=401, detail="Invalid Employee ID or password")
    if not emp.get("is_active", True):
        raise HTTPException(status_code=403, detail="Account is deactivated")
    if not verify_password(req.password, emp["password_hash"]):
        raise HTTPException(status_code=401, detail="Invalid Employee ID or password")
    token = create_token(emp["employee_id"], emp["role"])
    return {
        "token": token,
        "employee": {
            "employee_id": emp["employee_id"],
            "name": emp["name"],
            "role": emp["role"]
        }
    }

@api_router.post("/auth/register")
async def register(req: RegisterRequest):
    # Validate fields
    if not req.name.strip():
        raise HTTPException(status_code=400, detail="Name is required")
    if not req.email.strip() or "@" not in req.email:
        raise HTTPException(status_code=400, detail="Valid email is required")
    if not req.mobile.strip() or len(req.mobile.strip()) < 10:
        raise HTTPException(status_code=400, detail="Valid mobile number is required (min 10 digits)")
    if not req.employee_id.strip():
        raise HTTPException(status_code=400, detail="Employee ID is required")
    if len(req.password) < 6:
        raise HTTPException(status_code=400, detail="Password must be at least 6 characters")

    # Check duplicate employee_id
    existing_id = await db.employees.find_one({"employee_id": req.employee_id.strip()})
    if existing_id:
        raise HTTPException(status_code=400, detail="Employee ID already exists")

    # Check duplicate email
    existing_email = await db.employees.find_one({"email": req.email.strip().lower()})
    if existing_email:
        raise HTTPException(status_code=400, detail="Email already registered")

    # Check duplicate mobile
    existing_mobile = await db.employees.find_one({"mobile": req.mobile.strip()})
    if existing_mobile:
        raise HTTPException(status_code=400, detail="Mobile number already registered")

    emp = {
        "id": str(uuid.uuid4()),
        "employee_id": req.employee_id.strip(),
        "name": req.name.strip(),
        "email": req.email.strip().lower(),
        "mobile": req.mobile.strip(),
        "password_hash": hash_password(req.password),
        "role": "employee",
        "is_active": True,
        "created_at": datetime.now(timezone.utc).isoformat()
    }
    await db.employees.insert_one(emp)
    logger.info(f"New employee registered: {req.employee_id}")
    return {"message": "Registration successful. You can now login.", "employee_id": req.employee_id.strip()}

@api_router.post("/auth/forgot-password")
async def forgot_password(req: ForgotPasswordRequest):
    emp = await db.employees.find_one({"employee_id": req.employee_id}, {"_id": 0})
    if not emp:
        raise HTTPException(status_code=404, detail="Employee ID not found")
    reset_token = secrets.token_hex(16)
    await db.employees.update_one(
        {"employee_id": req.employee_id},
        {"$set": {"reset_token": reset_token, "reset_token_expiry": (datetime.now(timezone.utc) + timedelta(hours=1)).isoformat()}}
    )
    return {"message": "Password reset initiated. Contact admin for reset token.", "reset_token": reset_token}

@api_router.post("/auth/reset-password")
async def reset_password(req: ResetPasswordRequest):
    emp = await db.employees.find_one({"employee_id": req.employee_id}, {"_id": 0})
    if not emp or emp.get("reset_token") != req.reset_token:
        raise HTTPException(status_code=400, detail="Invalid reset token")
    await db.employees.update_one(
        {"employee_id": req.employee_id},
        {"$set": {"password_hash": hash_password(req.new_password)}, "$unset": {"reset_token": "", "reset_token_expiry": ""}}
    )
    return {"message": "Password reset successful"}

# --- Attendance Endpoints ---
@api_router.get("/attendance/status")
async def attendance_status(request: Request):
    user = await get_current_user(request)
    today = get_ist_today_str()
    records = await db.attendance.find(
        {"employee_id": user["employee_id"], "date": today}, {"_id": 0}
    ).to_list(10)
    
    has_login = any(r["action"] == "Login" for r in records)
    has_logout = any(r["action"] == "Logout" for r in records)
    
    login_record = next((r for r in records if r["action"] == "Login"), None)
    logout_record = next((r for r in records if r["action"] == "Logout"), None)
    
    return {
        "date": today,
        "has_login": has_login,
        "has_logout": has_logout,
        "can_login": not has_login,
        "can_logout": has_login and not has_logout,
        "login_time": login_record["time"] if login_record else None,
        "logout_time": logout_record["time"] if logout_record else None,
        "login_location": login_record.get("location_url") if login_record else None,
        "logout_location": logout_record.get("location_url") if logout_record else None,
    }

@api_router.post("/attendance/login")
async def mark_login(req: AttendanceRequest, request: Request):
    user = await get_current_user(request)
    today = get_ist_today_str()
    
    existing = await db.attendance.find_one({"employee_id": user["employee_id"], "date": today, "action": "Login"})
    if existing:
        raise HTTPException(status_code=400, detail="Already logged in today")
    
    now_ist = get_ist_now()
    time_str = now_ist.strftime("%I:%M:%S %p")
    location_url = f"https://maps.google.com/?q={req.latitude},{req.longitude}"
    
    record = {
        "id": str(uuid.uuid4()),
        "employee_id": user["employee_id"],
        "name": user["name"],
        "date": today,
        "action": "Login",
        "time": time_str,
        "timestamp": now_ist.isoformat(),
        "latitude": req.latitude,
        "longitude": req.longitude,
        "location_url": location_url,
        "ip_address": request.client.host if request.client else "unknown",
        "is_auto_logout": False,
    }
    
    await db.attendance.insert_one({k: v for k, v in record.items()})
    
    # Push to Google Sheets
    sheets_data = {
        "date": today,
        "employee_id": user["employee_id"],
        "name": user["name"],
        "action": "Login",
        "time": time_str,
        "location_url": location_url,
        "latitude": str(req.latitude),
        "longitude": str(req.longitude),
        "ip_address": request.client.host if request.client else "unknown",
        "auto_logout": "No"
    }
    await push_to_google_sheets(sheets_data)
    
    return {"message": "Login marked successfully", "time": time_str, "location_url": location_url}

@api_router.post("/attendance/logout")
async def mark_logout(req: AttendanceRequest, request: Request):
    user = await get_current_user(request)
    today = get_ist_today_str()
    
    login_exists = await db.attendance.find_one({"employee_id": user["employee_id"], "date": today, "action": "Login"})
    if not login_exists:
        raise HTTPException(status_code=400, detail="Must login first before logout")
    
    logout_exists = await db.attendance.find_one({"employee_id": user["employee_id"], "date": today, "action": "Logout"})
    if logout_exists:
        raise HTTPException(status_code=400, detail="Already logged out today")
    
    now_ist = get_ist_now()
    time_str = now_ist.strftime("%I:%M:%S %p")
    location_url = f"https://maps.google.com/?q={req.latitude},{req.longitude}"
    
    record = {
        "id": str(uuid.uuid4()),
        "employee_id": user["employee_id"],
        "name": user["name"],
        "date": today,
        "action": "Logout",
        "time": time_str,
        "timestamp": now_ist.isoformat(),
        "latitude": req.latitude,
        "longitude": req.longitude,
        "location_url": location_url,
        "ip_address": request.client.host if request.client else "unknown",
        "is_auto_logout": False,
    }
    
    await db.attendance.insert_one({k: v for k, v in record.items()})
    
    sheets_data = {
        "date": today,
        "employee_id": user["employee_id"],
        "name": user["name"],
        "action": "Logout",
        "time": time_str,
        "location_url": location_url,
        "latitude": str(req.latitude),
        "longitude": str(req.longitude),
        "ip_address": request.client.host if request.client else "unknown",
        "auto_logout": "No"
    }
    await push_to_google_sheets(sheets_data)
    
    return {"message": "Logout marked successfully", "time": time_str, "location_url": location_url}

@api_router.get("/attendance/history")
async def attendance_history(request: Request, days: int = 30):
    user = await get_current_user(request)
    records = await db.attendance.find(
        {"employee_id": user["employee_id"]}, {"_id": 0}
    ).sort("timestamp", -1).to_list(days * 2)
    return {"records": records}

# --- Auto Logout ---
@api_router.post("/attendance/auto-logout")
async def auto_logout():
    """Auto logout all employees who logged in but haven't logged out today"""
    today = get_ist_today_str()
    
    login_records = await db.attendance.find(
        {"date": today, "action": "Login"}, {"_id": 0}
    ).to_list(1000)
    
    auto_logout_count = 0
    for login_rec in login_records:
        logout_exists = await db.attendance.find_one(
            {"employee_id": login_rec["employee_id"], "date": today, "action": "Logout"}
        )
        if not logout_exists:
            now_ist = get_ist_now()
            time_str = "11:59:00 PM"
            record = {
                "id": str(uuid.uuid4()),
                "employee_id": login_rec["employee_id"],
                "name": login_rec["name"],
                "date": today,
                "action": "Logout",
                "time": time_str,
                "timestamp": now_ist.isoformat(),
                "latitude": login_rec.get("latitude", 0),
                "longitude": login_rec.get("longitude", 0),
                "location_url": login_rec.get("location_url", ""),
                "ip_address": "auto-system",
                "is_auto_logout": True,
            }
            await db.attendance.insert_one({k: v for k, v in record.items()})
            
            sheets_data = {
                "date": today,
                "employee_id": login_rec["employee_id"],
                "name": login_rec["name"],
                "action": "Logout",
                "time": time_str,
                "location_url": login_rec.get("location_url", ""),
                "latitude": str(login_rec.get("latitude", 0)),
                "longitude": str(login_rec.get("longitude", 0)),
                "ip_address": "auto-system",
                "auto_logout": "Yes"
            }
            await push_to_google_sheets(sheets_data)
            auto_logout_count += 1
    
    return {"message": f"Auto logout completed for {auto_logout_count} employees"}

# --- Admin Endpoints ---
@api_router.get("/admin/employees")
async def list_employees(request: Request):
    await require_admin(request)
    employees = await db.employees.find({}, {"_id": 0, "password_hash": 0, "reset_token": 0, "reset_token_expiry": 0}).to_list(1000)
    return {"employees": employees}

@api_router.post("/admin/employees")
async def create_employee(req: EmployeeCreate, request: Request):
    await require_admin(request)
    existing = await db.employees.find_one({"employee_id": req.employee_id})
    if existing:
        raise HTTPException(status_code=400, detail="Employee ID already exists")
    emp = {
        "id": str(uuid.uuid4()),
        "employee_id": req.employee_id,
        "name": req.name,
        "password_hash": hash_password(req.password),
        "role": req.role,
        "is_active": True,
        "created_at": datetime.now(timezone.utc).isoformat()
    }
    await db.employees.insert_one(emp)
    return {"message": "Employee created", "employee_id": req.employee_id}

@api_router.put("/admin/employees/{employee_id}")
async def update_employee(employee_id: str, req: EmployeeUpdate, request: Request):
    await require_admin(request)
    update_data = {k: v for k, v in req.dict().items() if v is not None}
    if not update_data:
        raise HTTPException(status_code=400, detail="No fields to update")
    result = await db.employees.update_one({"employee_id": employee_id}, {"$set": update_data})
    if result.matched_count == 0:
        raise HTTPException(status_code=404, detail="Employee not found")
    return {"message": "Employee updated"}

@api_router.delete("/admin/employees/{employee_id}")
async def delete_employee(employee_id: str, request: Request):
    await require_admin(request)
    if employee_id == "ADMIN001":
        raise HTTPException(status_code=400, detail="Cannot delete primary admin")
    result = await db.employees.delete_one({"employee_id": employee_id})
    if result.deleted_count == 0:
        raise HTTPException(status_code=404, detail="Employee not found")
    return {"message": "Employee deleted"}

@api_router.post("/admin/change-password")
async def admin_change_password(req: ChangePasswordRequest, request: Request):
    await require_admin(request)
    result = await db.employees.update_one(
        {"employee_id": req.employee_id},
        {"$set": {"password_hash": hash_password(req.new_password)}}
    )
    if result.matched_count == 0:
        raise HTTPException(status_code=404, detail="Employee not found")
    return {"message": "Password changed successfully"}

@api_router.get("/admin/attendance")
async def admin_attendance(request: Request, date: Optional[str] = None):
    await require_admin(request)
    query = {}
    if date:
        query["date"] = date
    else:
        query["date"] = get_ist_today_str()
    records = await db.attendance.find(query, {"_id": 0}).sort("timestamp", -1).to_list(1000)
    return {"records": records, "date": query["date"]}

@api_router.get("/health")
async def health():
    return {"status": "ok", "timestamp": datetime.now(timezone.utc).isoformat()}

app.include_router(api_router)

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

@app.on_event("shutdown")
async def shutdown_db_client():
    client.close()
