from datetime import datetime, timedelta, date, timezone
from typing import Optional
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, or_
from fastapi import HTTPException, status, Request

from app.models.user import User, UserToken, StudentProfile, ParentProfile, TeacherProfile, UserRole
from app.models.school import School, SchoolPaymentAccount
from app.models.payment import Subscription
from app.models.academic import Package
from app.core.security import (
    hash_password, verify_password, create_access_token,
    create_refresh_token, generate_stem_id, generate_access_code,
    generate_secure_token, hash_token, decode_token
)
from app.core.config import settings
from app.schemas.auth import (
    RegisterStudentRequest, RegisterParentRequest, RegisterSchoolRequest,
    RegisterTeacherRequest, RegisterOrganizationRequest, TokenResponse
)


async def get_user_by_identifier(db: AsyncSession, identifier: str) -> Optional[User]:
    """Find user by email, phone, username, or stem_id."""
    result = await db.execute(
        select(User).where(
            or_(
                User.email == identifier,
                User.phone == identifier,
                User.username == identifier,
                User.stem_id == identifier,
            )
        )
    )
    return result.scalar_one_or_none()


async def authenticate_user(
    db: AsyncSession, identifier: str, password: str, request: Request
) -> User:
    """Authenticate a user and handle login attempts/locking."""
    user = await get_user_by_identifier(db, identifier)

    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid credentials",
        )

    # Check if account is locked
    if user.locked_until and user.locked_until > datetime.now(timezone.utc):
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail=f"Account locked. Try again after {user.locked_until.strftime('%H:%M')}",
        )

    if not verify_password(password, user.password_hash):
        user.failed_login_attempts += 1
        if user.failed_login_attempts >= settings.MAX_LOGIN_ATTEMPTS:
            user.locked_until = datetime.now(timezone.utc) + timedelta(
                minutes=settings.ACCOUNT_LOCK_MINUTES
            )
            user.failed_login_attempts = 0
        await db.commit()
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid credentials",
        )

    if not user.is_active:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Account is deactivated. Contact support.",
        )

    # Reset failed attempts on success
    user.failed_login_attempts = 0
    user.locked_until = None
    user.last_login_at = datetime.now(timezone.utc)
    await db.commit()
    return user


def build_token_response(user: User) -> TokenResponse:
    access_token = create_access_token(subject=user.id, role=user.role)
    refresh_token = create_refresh_token(subject=user.id)
    return TokenResponse(
        access_token=access_token,
        refresh_token=refresh_token,
        role=user.role,
        stem_id=user.stem_id,
        user_id=user.id,
        full_name=user.full_name,
    )


async def register_student(db: AsyncSession, data: RegisterStudentRequest) -> User:
    """Register a new student account with freemium subscription."""
    # Check uniqueness
    if data.email:
        existing = await db.execute(select(User).where(User.email == data.email))
        if existing.scalar_one_or_none():
            raise HTTPException(status_code=400, detail="Email already registered")

    stem_id = generate_stem_id("STU")
    user = User(
        stem_id=stem_id,
        email=data.email,
        phone=data.phone,
        password_hash=hash_password(data.password),
        first_name=data.first_name,
        last_name=data.last_name,
        role=UserRole.student,
    )
    db.add(user)
    await db.flush()

    profile = StudentProfile(user_id=user.id, current_grade_id=data.grade_id)
    db.add(profile)

    # Auto-subscribe to freemium
    result = await db.execute(select(Package).where(Package.slug == "freemium"))
    pkg = result.scalar_one_or_none()
    if pkg:
        sub = Subscription(
            user_id=user.id,
            package_id=pkg.id,
            start_date=date.today(),
            status="active",
        )
        db.add(sub)

    await db.commit()
    await db.refresh(user)
    return user


async def register_parent(db: AsyncSession, data: RegisterParentRequest) -> User:
    existing = await db.execute(select(User).where(User.email == data.email))
    if existing.scalar_one_or_none():
        raise HTTPException(status_code=400, detail="Email already registered")

    stem_id = generate_stem_id("PAR")
    user = User(
        stem_id=stem_id,
        email=data.email,
        phone=data.phone,
        password_hash=hash_password(data.password),
        first_name=data.first_name,
        last_name=data.last_name,
        role=UserRole.parent,
    )
    db.add(user)
    await db.flush()

    profile = ParentProfile(user_id=user.id, occupation=data.occupation)
    db.add(profile)
    await db.commit()
    await db.refresh(user)
    return user


async def register_teacher(db: AsyncSession, data: RegisterTeacherRequest) -> User:
    existing = await db.execute(select(User).where(User.email == data.email))
    if existing.scalar_one_or_none():
        raise HTTPException(status_code=400, detail="Email already registered")

    stem_id = generate_stem_id("TCH")
    teacher_code = generate_stem_id("TC")

    user = User(
        stem_id=stem_id,
        email=data.email,
        phone=data.phone,
        password_hash=hash_password(data.password),
        first_name=data.first_name,
        last_name=data.last_name,
        role=UserRole.teacher,
    )
    db.add(user)
    await db.flush()

    profile = TeacherProfile(
        user_id=user.id,
        teacher_code=teacher_code,
        specialization=data.specialization,
        qualifications=data.qualifications,
        experience_years=data.experience_years,
    )
    db.add(profile)
    await db.commit()
    await db.refresh(user)
    return user


async def register_school(db: AsyncSession, data: RegisterSchoolRequest) -> User:
    existing = await db.execute(select(User).where(User.email == data.email))
    if existing.scalar_one_or_none():
        raise HTTPException(status_code=400, detail="Email already registered")

    stem_id = generate_stem_id("SCH")
    school_code = generate_stem_id("SC")[:10]
    access_code = generate_access_code()

    user = User(
        stem_id=stem_id,
        email=data.email,
        phone=data.phone,
        password_hash=hash_password(data.password),
        first_name=data.school_name,
        last_name="School",
        role=UserRole.school,
    )
    db.add(user)
    await db.flush()

    from app.models.school import School, SchoolPaymentAccount
    school = School(
        user_id=user.id,
        school_name=data.school_name,
        school_code=school_code,
        school_type=data.school_type,
        address=data.address,
        city=data.city,
        country=data.country,
        contact_email=data.contact_email or data.email,
        contact_phone=data.contact_phone or data.phone,
        registration_number=data.registration_number,
        access_code=access_code,
    )
    db.add(school)
    await db.flush()

    account = SchoolPaymentAccount(school_id=school.id)
    db.add(account)

    await db.commit()
    await db.refresh(user)
    return user
