"""
auth.py — Authentication, session management, password hashing.
No JWT — pure server-side sessions in SQLite for simplicity & cPanel compat.
"""
import bcrypt, secrets, os
from datetime import datetime, timedelta
from database import get_db
from fastapi import Request, HTTPException
from fastapi.responses import RedirectResponse

SESSION_COOKIE = "tt_session"
SESSION_DAYS   = 30


# ── Password ──────────────────────────────────────────────────────────────────
def hash_password(plain: str) -> str:
    return bcrypt.hashpw(plain.encode(), bcrypt.gensalt(rounds=12)).decode()

def verify_password(plain: str, hashed: str) -> bool:
    try:
        return bcrypt.checkpw(plain.encode(), hashed.encode())
    except Exception:
        return False


# ── Session ───────────────────────────────────────────────────────────────────
def create_session(user_id: int, school_id: int) -> str:
    token = secrets.token_urlsafe(48)
    expires = (datetime.utcnow() + timedelta(days=SESSION_DAYS)).isoformat()
    db = get_db()
    # Clean old sessions for this user
    db.execute("DELETE FROM session WHERE user_id=?", (user_id,))
    db.execute(
        "INSERT INTO session (token, user_id, school_id, expires_at) VALUES (?,?,?,?)",
        (token, user_id, school_id, expires)
    )
    db.execute(
        "UPDATE user SET last_login=datetime('now') WHERE id=?", (user_id,)
    )
    db.commit()
    db.close()
    return token

def get_session(token: str) -> dict | None:
    if not token:
        return None
    db = get_db()
    row = db.execute(
        """SELECT s.*, u.name as user_name, u.email as user_email,
                  u.role, sc.name as school_name, sc.board, sc.is_active as school_active
           FROM session s
           JOIN user u ON u.id = s.user_id
           JOIN school sc ON sc.id = s.school_id
           WHERE s.token=? AND s.expires_at > datetime('now')""",
        (token,)
    ).fetchone()
    db.close()
    return dict(row) if row else None

def delete_session(token: str):
    db = get_db()
    db.execute("DELETE FROM session WHERE token=?", (token,))
    db.commit()
    db.close()

def purge_expired_sessions():
    db = get_db()
    db.execute("DELETE FROM session WHERE expires_at < datetime('now')")
    db.commit()
    db.close()


# ── Request helpers ───────────────────────────────────────────────────────────
def get_current_user(request: Request) -> dict | None:
    token = request.cookies.get(SESSION_COOKIE)
    return get_session(token) if token else None

def require_auth(request: Request) -> dict:
    """Call this in every protected route. Returns session dict or raises redirect."""
    user = get_current_user(request)
    if not user:
        raise HTTPException(status_code=303, headers={"Location": "/login"})
    return user

def require_role(request: Request, roles: list[str]) -> dict:
    user = require_auth(request)
    if user["role"] not in roles:
        raise HTTPException(status_code=403, detail="Insufficient permissions")
    return user


# ── Registration ──────────────────────────────────────────────────────────────
def register_school(
    school_name: str, board: str, city: str, state: str,
    admin_name: str, admin_email: str, admin_password: str
) -> tuple[bool, str, int | None]:
    """
    Creates a school + owner user atomically.
    Returns (success, message, school_id).
    """
    db = get_db()
    try:
        # Check email not taken
        existing = db.execute(
            "SELECT id FROM user WHERE email=?", (admin_email.lower().strip(),)
        ).fetchone()
        if existing:
            return False, "This email is already registered.", None

        # Create school
        cur = db.execute(
            "INSERT INTO school (name, board, city, state) VALUES (?,?,?,?)",
            (school_name.strip(), board, city.strip(), state.strip())
        )
        school_id = cur.lastrowid

        # Create owner user
        db.execute(
            "INSERT INTO user (school_id, name, email, password_hash, role) VALUES (?,?,?,?,?)",
            (school_id, admin_name.strip(), admin_email.lower().strip(),
             hash_password(admin_password), "owner")
        )
        db.commit()
        return True, "Registration successful!", school_id
    except Exception as e:
        db.rollback()
        return False, f"Registration failed: {e}", None
    finally:
        db.close()


# ── Login ─────────────────────────────────────────────────────────────────────
def login_user(email: str, password: str) -> tuple[bool, str, dict | None]:
    """Returns (success, message, user_row)."""
    db = get_db()
    try:
        user = db.execute(
            """SELECT u.*, s.name as school_name, s.is_active as school_active
               FROM user u JOIN school s ON s.id=u.school_id
               WHERE u.email=?""",
            (email.lower().strip(),)
        ).fetchone()

        if not user:
            return False, "Invalid email or password.", None
        if not verify_password(password, user["password_hash"]):
            return False, "Invalid email or password.", None
        if not user["is_active"]:
            return False, "Your account has been deactivated. Contact your school admin.", None
        if not user["school_active"]:
            return False, "Your school account is inactive. Contact EduEval support.", None

        return True, "Login successful!", dict(user)
    finally:
        db.close()


# ── Invite User (owner adds staff) ────────────────────────────────────────────
def invite_user(
    school_id: int, name: str, email: str,
    password: str, role: str
) -> tuple[bool, str]:
    db = get_db()
    try:
        existing = db.execute("SELECT id FROM user WHERE email=?", (email.lower(),)).fetchone()
        if existing:
            return False, "Email already registered."
        db.execute(
            "INSERT INTO user (school_id,name,email,password_hash,role) VALUES (?,?,?,?,?)",
            (school_id, name.strip(), email.lower(), hash_password(password), role)
        )
        db.commit()
        return True, f"User {name} added successfully."
    except Exception as e:
        return False, str(e)
    finally:
        db.close()
