from datetime import datetime, timezone

from fastapi import HTTPException
from jose import JWTError
from sqlalchemy import or_, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.security import (
    create_token,
    decode_token,
    hash_password,
    verify_password,
)
from app.models.user import User, UserRole, UserStatus
from app.schemas.token import TokenResponse
from app.schemas.user import (
    LoginByCodeRequest,
    LoginBySmsRequest,
    SendCodeRequest,
    SendSmsRequest,
    UserLogin,
    UserRegister,
    UserRegisterBySms,
    UserResponse,
)
from app.services import (
    captcha_service,
    email_service,
    session_service,
    sms_service,
    system_setting_service,
    verification_service,
)


async def _ensure_registration_enabled(db: AsyncSession):
    business_settings = await system_setting_service.get_business_settings(db)
    if not business_settings.registration_enabled:
        raise HTTPException(status_code=403, detail="当前暂停新用户注册")
    return business_settings


async def send_register_code(db: AsyncSession, data: SendCodeRequest) -> int:
    """发送注册验证码。返回剩余冷却秒数。"""
    await _ensure_registration_enabled(db)
    await captcha_service.verify(data.captcha.model_dump() if data.captcha else None)

    existing = await db.execute(select(User).where(User.email == data.email))
    if existing.scalar_one_or_none():
        raise HTTPException(status_code=400, detail="该邮箱已被注册")

    remaining = await verification_service.can_send("email_register", data.email)
    if remaining > 0:
        raise HTTPException(status_code=429, detail=f"请 {remaining} 秒后重试")

    code = verification_service.generate_code()
    await verification_service.save_code("email_register", data.email, code)
    await email_service.send_verification_code(data.email, code, purpose="register")
    return verification_service.SEND_COOLDOWN


async def send_login_code(db: AsyncSession, data: SendCodeRequest) -> int:
    """发送登录验证码。邮箱必须已注册。"""
    await captcha_service.verify(data.captcha.model_dump() if data.captcha else None)

    result = await db.execute(select(User).where(User.email == data.email))
    user = result.scalar_one_or_none()
    if not user:
        raise HTTPException(status_code=400, detail="该邮箱尚未注册")
    if user.status == UserStatus.BANNED:
        raise HTTPException(status_code=403, detail="账户已被封禁")

    remaining = await verification_service.can_send("email_login", data.email)
    if remaining > 0:
        raise HTTPException(status_code=429, detail=f"请 {remaining} 秒后重试")

    code = verification_service.generate_code()
    await verification_service.save_code("email_login", data.email, code)
    await email_service.send_verification_code(data.email, code, purpose="login")
    return verification_service.SEND_COOLDOWN


async def _bind_invite_code(db: AsyncSession, user: User, invite_code: str) -> None:
    """注册时根据邀请码永久绑定推广关系。"""
    from app.services import agent_service as _agent_svc
    from app.models.affiliate_click import AffiliateClick

    agent_user = await _agent_svc.resolve_agent_from_code(db, invite_code.strip().upper())
    if not agent_user:
        return

    # 防止自绑定
    if agent_user.id == user.id:
        return

    from app.models.user_agent import AgentStatus, UserAgent as UAModel

    user.bound_agent_id = agent_user.id

    # 只有激活代理才进入代理客户表；普通推广只保留 users.bound_agent_id。
    ua_res = await db.execute(
        select(UAModel).where(
            UAModel.user_id == agent_user.id,
            UAModel.status.in_([AgentStatus.ACTIVE, AgentStatus.OBSERVING]),
        )
    )
    if ua_res.scalar_one_or_none():
        await _agent_svc.bind_customer_to_agent(
            db, agent_user.id, user.id, bind_source="invite_code"
        )

    # 更新 AFF 点击记录的转化
    click_res = await db.execute(
        select(AffiliateClick)
        .where(
            AffiliateClick.aff_code == invite_code.strip().upper(),
            AffiliateClick.registered_user_id.is_(None),
        )
        .order_by(AffiliateClick.id.desc())
        .limit(1)
    )
    click = click_res.scalar_one_or_none()
    if click:
        click.registered_user_id = user.id
        click.converted_at = datetime.now(timezone.utc)


async def register(db: AsyncSession, data: UserRegister) -> tuple[UserResponse, TokenResponse]:
    business_settings = await _ensure_registration_enabled(db)
    await captcha_service.verify(data.captcha.model_dump() if data.captcha else None)

    ok = await verification_service.verify_code("email_register", data.email, data.code)
    if not ok:
        raise HTTPException(status_code=400, detail="验证码错误或已失效")

    existing = await db.execute(
        select(User).where(or_(User.email == data.email, User.username == data.username))
    )
    if existing.scalar_one_or_none():
        raise HTTPException(status_code=400, detail="邮箱或用户名已被注册")

    plain_password = data.password

    user = User(
        email=data.email,
        username=data.username,
        hashed_password=hash_password(plain_password) if plain_password else None,
        is_email_verified=True,
    )
    db.add(user)
    await db.flush()
    await db.refresh(user)

    # ── 绑定代理关系（邀请码注册） ──
    if data.invite_code:
        await _bind_invite_code(db, user, data.invite_code)

    tokens = await _create_tokens_with_redis(user)

    await email_service.send_welcome(
        data.email,
        data.username,
        password_set=bool(plain_password),
        frontend_url=business_settings.frontend_url,
    )

    return UserResponse.model_validate(user), tokens


async def login(db: AsyncSession, data: UserLogin) -> tuple[UserResponse, TokenResponse]:
    await captcha_service.verify(data.captcha.model_dump() if data.captcha else None)

    result = await db.execute(
        select(User).where(
            or_(User.email == data.email, User.username == data.email, User.phone == data.email)
        )
    )
    user = result.scalar_one_or_none()

    if not user or not user.hashed_password or not verify_password(data.password, user.hashed_password):
        raise HTTPException(status_code=401, detail="账号或密码错误")

    if user.status == UserStatus.BANNED:
        raise HTTPException(status_code=403, detail="账户已被封禁")

    user.last_login_at = datetime.now(timezone.utc)
    await db.flush()

    tokens = await _create_tokens_with_redis(user)
    return UserResponse.model_validate(user), tokens


async def admin_login(db: AsyncSession, data: UserLogin) -> tuple[UserResponse, TokenResponse]:
    """后台登录：仅 admin / super_admin 角色可通过。"""
    await captcha_service.verify(data.captcha.model_dump() if data.captcha else None)

    result = await db.execute(
        select(User).where(
            or_(User.email == data.email, User.username == data.email, User.phone == data.email)
        )
    )
    user = result.scalar_one_or_none()

    if not user or not user.hashed_password or not verify_password(data.password, user.hashed_password):
        raise HTTPException(status_code=401, detail="账号或密码错误")

    if user.status == UserStatus.BANNED:
        raise HTTPException(status_code=403, detail="账户已被封禁")

    if user.role not in (UserRole.ADMIN, UserRole.SUPER_ADMIN):
        raise HTTPException(status_code=403, detail="该账号无后台访问权限")

    user.last_login_at = datetime.now(timezone.utc)
    await db.flush()

    tokens = await _create_tokens_with_redis(user)
    return UserResponse.model_validate(user), tokens


async def login_by_code(db: AsyncSession, data: LoginByCodeRequest) -> tuple[UserResponse, TokenResponse]:
    """邮箱验证码登录：不依赖密码，未设置密码的账户也可使用。"""
    await captcha_service.verify(data.captcha.model_dump() if data.captcha else None)

    result = await db.execute(select(User).where(User.email == data.email))
    user = result.scalar_one_or_none()
    if not user:
        raise HTTPException(status_code=400, detail="该邮箱尚未注册")

    if user.status == UserStatus.BANNED:
        raise HTTPException(status_code=403, detail="账户已被封禁")

    ok = await verification_service.verify_code("email_login", data.email, data.code)
    if not ok:
        raise HTTPException(status_code=400, detail="验证码错误或已失效")

    user.last_login_at = datetime.now(timezone.utc)
    await db.flush()

    tokens = await _create_tokens_with_redis(user)
    return UserResponse.model_validate(user), tokens


async def send_register_sms_code(db: AsyncSession, data: SendSmsRequest) -> int:
    """发送注册短信验证码。手机号已注册则拒绝。"""
    await _ensure_registration_enabled(db)
    await captcha_service.verify(data.captcha.model_dump() if data.captcha else None)

    existing = await db.execute(select(User).where(User.phone == data.phone))
    if existing.scalar_one_or_none():
        raise HTTPException(status_code=400, detail="该手机号已被注册")

    remaining = await verification_service.can_send("sms_register", data.phone)
    if remaining > 0:
        raise HTTPException(status_code=429, detail=f"请 {remaining} 秒后重试")

    code = verification_service.generate_code()
    await verification_service.save_code("sms_register", data.phone, code)
    await sms_service.send_verification_code(data.phone, code, purpose="register")
    return verification_service.SEND_COOLDOWN


async def send_login_sms_code(db: AsyncSession, data: SendSmsRequest) -> int:
    """发送登录短信验证码。手机号必须已注册。"""
    await captcha_service.verify(data.captcha.model_dump() if data.captcha else None)

    result = await db.execute(select(User).where(User.phone == data.phone))
    user = result.scalar_one_or_none()
    if not user:
        raise HTTPException(status_code=400, detail="该手机号尚未注册")
    if user.status == UserStatus.BANNED:
        raise HTTPException(status_code=403, detail="账户已被封禁")

    remaining = await verification_service.can_send("sms_login", data.phone)
    if remaining > 0:
        raise HTTPException(status_code=429, detail=f"请 {remaining} 秒后重试")

    code = verification_service.generate_code()
    await verification_service.save_code("sms_login", data.phone, code)
    await sms_service.send_verification_code(data.phone, code, purpose="login")
    return verification_service.SEND_COOLDOWN


async def register_by_sms(db: AsyncSession, data: UserRegisterBySms) -> tuple[UserResponse, TokenResponse]:
    await _ensure_registration_enabled(db)
    await captcha_service.verify(data.captcha.model_dump() if data.captcha else None)

    ok = await verification_service.verify_code("sms_register", data.phone, data.code)
    if not ok:
        raise HTTPException(status_code=400, detail="验证码错误或已失效")

    existing = await db.execute(
        select(User).where(or_(User.phone == data.phone, User.username == data.username))
    )
    if existing.scalar_one_or_none():
        raise HTTPException(status_code=400, detail="手机号或用户名已被注册")

    plain_password = data.password
    user = User(
        phone=data.phone,
        username=data.username,
        hashed_password=hash_password(plain_password) if plain_password else None,
        is_phone_verified=True,
    )
    db.add(user)
    await db.flush()
    await db.refresh(user)

    # ── 绑定代理关系（邀请码注册） ──
    if data.invite_code:
        await _bind_invite_code(db, user, data.invite_code)

    tokens = await _create_tokens_with_redis(user)
    return UserResponse.model_validate(user), tokens


async def login_by_sms(db: AsyncSession, data: LoginBySmsRequest) -> tuple[UserResponse, TokenResponse]:
    await captcha_service.verify(data.captcha.model_dump() if data.captcha else None)

    result = await db.execute(select(User).where(User.phone == data.phone))
    user = result.scalar_one_or_none()
    if not user:
        raise HTTPException(status_code=400, detail="该手机号尚未注册")

    if user.status == UserStatus.BANNED:
        raise HTTPException(status_code=403, detail="账户已被封禁")

    ok = await verification_service.verify_code("sms_login", data.phone, data.code)
    if not ok:
        raise HTTPException(status_code=400, detail="验证码错误或已失效")

    user.last_login_at = datetime.now(timezone.utc)
    await db.flush()

    tokens = await _create_tokens_with_redis(user)
    return UserResponse.model_validate(user), tokens


async def send_bind_email_code(db: AsyncSession, user: User, data: SendCodeRequest) -> int:
    """发送邮箱绑定验证码。新邮箱不能等于当前邮箱，也不能被他人占用。"""
    await captcha_service.verify(data.captcha.model_dump() if data.captcha else None)

    if user.email and user.email.lower() == data.email.lower():
        raise HTTPException(status_code=400, detail="新邮箱与当前邮箱相同")

    existing = await db.execute(select(User).where(User.email == data.email))
    other = existing.scalar_one_or_none()
    if other and other.id != user.id:
        raise HTTPException(status_code=400, detail="该邮箱已被其他账户占用")

    remaining = await verification_service.can_send("email_bind", data.email)
    if remaining > 0:
        raise HTTPException(status_code=429, detail=f"请 {remaining} 秒后重试")

    code = verification_service.generate_code()
    await verification_service.save_code("email_bind", data.email, code)
    await email_service.send_verification_code(data.email, code, purpose="bind")
    return verification_service.SEND_COOLDOWN


async def send_bind_sms_code(db: AsyncSession, user: User, data: SendSmsRequest) -> int:
    """发送手机绑定验证码。新手机不能等于当前手机，也不能被他人占用。"""
    await captcha_service.verify(data.captcha.model_dump() if data.captcha else None)

    if user.phone and user.phone == data.phone:
        raise HTTPException(status_code=400, detail="新手机号与当前手机号相同")

    existing = await db.execute(select(User).where(User.phone == data.phone))
    other = existing.scalar_one_or_none()
    if other and other.id != user.id:
        raise HTTPException(status_code=400, detail="该手机号已被其他账户占用")

    remaining = await verification_service.can_send("sms_bind", data.phone)
    if remaining > 0:
        raise HTTPException(status_code=429, detail=f"请 {remaining} 秒后重试")

    code = verification_service.generate_code()
    await verification_service.save_code("sms_bind", data.phone, code)
    await sms_service.send_verification_code(data.phone, code, purpose="bind")
    return verification_service.SEND_COOLDOWN


async def bind_email(db: AsyncSession, user: User, email: str, code: str) -> User:
    if user.email and user.email.lower() == email.lower():
        raise HTTPException(status_code=400, detail="新邮箱与当前邮箱相同")

    existing = await db.execute(select(User).where(User.email == email))
    other = existing.scalar_one_or_none()
    if other and other.id != user.id:
        raise HTTPException(status_code=400, detail="该邮箱已被其他账户占用")

    ok = await verification_service.verify_code("email_bind", email, code)
    if not ok:
        raise HTTPException(status_code=400, detail="验证码错误或已失效")

    user.email = email
    user.is_email_verified = True
    await db.flush()
    await db.refresh(user)
    return user


async def bind_phone(db: AsyncSession, user: User, phone: str, code: str) -> User:
    if user.phone and user.phone == phone:
        raise HTTPException(status_code=400, detail="新手机号与当前手机号相同")

    existing = await db.execute(select(User).where(User.phone == phone))
    other = existing.scalar_one_or_none()
    if other and other.id != user.id:
        raise HTTPException(status_code=400, detail="该手机号已被其他账户占用")

    ok = await verification_service.verify_code("sms_bind", phone, code)
    if not ok:
        raise HTTPException(status_code=400, detail="验证码错误或已失效")

    user.phone = phone
    user.is_phone_verified = True
    await db.flush()
    await db.refresh(user)
    return user


async def refresh(db: AsyncSession, refresh_token: str) -> TokenResponse:
    try:
        payload = decode_token(refresh_token)
    except JWTError:
        raise HTTPException(status_code=401, detail="Invalid refresh token")

    if payload.get("type") != "refresh":
        raise HTTPException(status_code=401, detail="Invalid token type")

    user_id = payload.get("sub")
    jti = payload.get("jti")
    if not user_id or not jti:
        raise HTTPException(status_code=401, detail="Invalid token payload")

    # Redis 白名单校验
    if not await session_service.exists_refresh(user_id, jti):
        raise HTTPException(status_code=401, detail="Refresh token revoked")

    result = await db.execute(select(User).where(User.id == int(user_id)))
    user = result.scalar_one_or_none()
    if not user:
        raise HTTPException(status_code=401, detail="User not found")

    if user.status == UserStatus.BANNED:
        raise HTTPException(status_code=403, detail="账户已被封禁")

    # 轮换：销毁旧 jti，签发新一对
    await session_service.revoke_refresh(user_id, jti)
    return await _create_tokens_with_redis(user)


async def logout(refresh_token: str) -> None:
    try:
        payload = decode_token(refresh_token)
    except JWTError:
        return
    user_id = payload.get("sub")
    jti = payload.get("jti")
    if user_id and jti:
        await session_service.revoke_refresh(user_id, jti)


async def _create_tokens_with_redis(user: User) -> TokenResponse:
    access_token, _ = create_token(str(user.id), user.role.value, "access")
    refresh_token, refresh_jti = create_token(str(user.id), user.role.value, "refresh")
    await session_service.add_refresh(user.id, refresh_jti)
    return TokenResponse(access_token=access_token, refresh_token=refresh_token)
