"""Authentication API routes."""

import uuid
from datetime import datetime, timezone
import uuid

from typing import Any

from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query, Request, status
from loguru import logger
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.security import create_access_token, get_authenticated_user, get_current_user, hash_password_async, verify_password_async
from app.database import get_db
from app.models.user import Identity, User
from app.schemas.schemas import (
    ForgotPasswordRequest,
    ResetPasswordRequest,
    IdentityBindRequest,
    IdentityUnbindRequest,
    OAuthAuthorizeResponse,
    OAuthCallbackRequest,
    TokenResponse,
    UserLogin,
    UserOut,
    UserRegister,
    UserUpdate,
    VerifyEmailRequest,
    ResendVerificationRequest,
    NeedsVerificationResponse,
    RegisterInitRequest,
    RegisterInitResponse,
    RegisterCompleteRequest,
    RegisterCompleteResponse,
    SSORegisterRequest,
    TenantChoice,
    MultiTenantResponse,
    IdentityOut,
    TenantSwitchRequest,
    TenantSwitchResponse,
)
from sqlalchemy.orm import selectinload

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


@router.get("/registration-config")
async def get_registration_config(db: AsyncSession = Depends(get_db)):
    """Public endpoint — returns registration requirements (no auth needed)."""
    from app.models.system_settings import SystemSetting
    result = await db.execute(select(SystemSetting).where(SystemSetting.key == "invitation_code_enabled"))
    setting = result.scalar_one_or_none()
    enabled = setting.value.get("enabled", False) if setting else False
    return {"invitation_code_required": enabled}


@router.get("/check-duplicate")
async def check_duplicate(
    email: str | None = Query(None, description="Email to check"),
    username: str | None = Query(None, description="Username to check"),
    db: AsyncSession = Depends(get_db),
):
    """Check if email or username already exists."""
    from app.models.user import Identity, User
    result = {"email_exists": False, "username_exists": False, "conflicts": []}

    if email:
        # Check Identity email
        existing = await db.execute(
            select(Identity).where(Identity.email == email)
        )
        if existing.scalar_one_or_none():
            result["email_exists"] = True
            result["conflicts"].append({"type": "email", "scope": "global", "message": "Email already registered"})

    if username:
        existing = await db.execute(select(Identity).where(Identity.username == username))
        if existing.scalar_one_or_none():
            result["username_exists"] = True
            result["conflicts"].append({"type": "username", "scope": "global", "message": "Username already taken"})

    result["has_conflict"] = result["email_exists"] or result["username_exists"]
    return result


async def _send_verification_email_task(
    user: User,
    background_tasks: BackgroundTasks,
    settings: Any,
    db: AsyncSession,
) -> None:
    """Helper to create verification token and add email task to background tasks."""
    # Check if email is configured — either via DB (platform settings UI) or env vars.
    # We must check the DB config too, since most users configure SMTP via the UI.
    from app.services.system_email_service import resolve_email_config_async
    email_config = await resolve_email_config_async(db)
    if not email_config:
        logger.debug("No email config found (env or DB), skipping verification email")
        return

    from app.services.email_verification_service import email_verification_service

    try:
        # Get identity for this user
        res = await db.execute(select(Identity).where(Identity.id == user.identity_id))
        identity = res.scalar_one_or_none()

        if not identity:
            logger.warning(f"No identity found for user {user.id} ({user.email}). Cannot send verification.")
            return

        raw_code, expires_at = await email_verification_service.create_email_verification_token(identity.id, identity.email)
        expiry_minutes = int((expires_at - datetime.now(timezone.utc)).total_seconds() // 60)
        
        background_tasks.add_task(
            email_verification_service.send_verification_email,
            identity.email,
            user.display_name or identity.username or "User",
            raw_code,
            expiry_minutes,
        )
    except Exception as exc:
        logger.error(f"Failed to create verification token for {user.email}: {exc}")
        logger.warning(f"Failed to send verification email for {user.email}: {exc}")


@router.post("/register", response_model=Any, status_code=status.HTTP_201_CREATED)
async def register(
    data: UserRegister,
    background_tasks: BackgroundTasks,
    db: AsyncSession = Depends(get_db),
):
    """Legacy registration endpoint - kept for backward compatibility.

    For new implementations, use:
    - /register/init - Step 1: Initialize registration
    - /register/sso - SSO registration
    - /verify-email - Step 3: Verify email
    """
    from app.config import get_settings
    settings = get_settings()

    # Handle SSO registration if provider info provided
    if data.provider and data.provider_code:
        return await _handle_sso_register(data, db)

    # Regular username/password registration - delegate to new flow
    return await _handle_normal_register(data, background_tasks, db, settings)
@router.post("/register/init", response_model=RegisterInitResponse, status_code=status.HTTP_201_CREATED)
async def register_init(
    data: RegisterInitRequest,
    background_tasks: BackgroundTasks,
    db: AsyncSession = Depends(get_db),
):
    """Step 1: Initialize registration with account credentials.

    Creates/finds a global Identity and a tenant-scoped User.
    """
    from app.config import get_settings
    settings = get_settings()
    from app.services.registration_service import registration_service
    from app.models.user import Identity, User

    logger.info(f"[REGISTER_INIT] Starting registration for email={data.email}")

    # Resolve email config once
    from app.services.system_email_service import resolve_email_config_async
    email_config = await resolve_email_config_async(db)

    # Check if this is the first user (platform admin setup) - Optimize with EXISTS
    is_first_user = (await db.execute(select(Identity.id).limit(1))).scalar() is None

    # Find or Create Identity
    identity = await registration_service.find_or_create_identity(
        db,
        email=data.email,
        username=data.username,
        password=data.password,
        is_platform_admin=is_first_user,
        email_config=email_config,
    )
    # Defense-in-depth: verify the returned identity actually belongs to the
    # submitted email. Under normal circumstances this should never trigger
    # (find_or_create_identity no longer uses username as a lookup key), but
    # this guard protects against future regressions.
    if identity.email and identity.email != data.email:
        logger.warning(
            "[REGISTER_INIT] Identity email mismatch: submitted=%s returned=%s — rejecting",
            data.email,
            identity.email,
        )
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="Username already taken. Please choose a different username.",
        )

    # If identity existed, verify password
    if identity.password_hash and not await verify_password_async(data.password, identity.password_hash):
         raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Email already registered. Incorrect password."
        )

    # For first user: auto-create/get default tenant
    tenant_uuid = None
    if is_first_user:
        from app.models.tenant import Tenant
        default = await db.execute(select(Tenant).where(Tenant.slug == "default"))
        tenant = default.scalar_one_or_none()
        if not tenant:
            tenant = Tenant(name="Default", slug="default", im_provider="web_only")
            db.add(tenant)
            await db.flush()
        tenant_uuid = tenant.id

    # Create User (tenant-scoped)
    # Check if user already exists in this tenant (if tenant_uuid is set)
    if tenant_uuid:
        existing_user_res = await db.execute(
            select(User).where(User.identity_id == identity.id, User.tenant_id == tenant_uuid)
        )
        user = existing_user_res.scalar_one_or_none()
    else:
        # Check for a "tenant-less" user (pending company setup)
        existing_user_res = await db.execute(
            select(User).where(User.identity_id == identity.id, User.tenant_id == None)
        )
        user = existing_user_res.scalar_one_or_none()

    if not user:
        user = await registration_service.create_user_with_identity(
            db,
            identity=identity,
            display_name=data.display_name or data.username,
            role="platform_admin" if is_first_user else "member",
            tenant_id=tenant_uuid,
        )
        # Set initial status
        user.is_active = is_first_user # Active immediately if first user
        user.email_verified = identity.email_verified
        await db.flush()

    # Generate token
    token = create_access_token(str(user.id), user.role)

    # Send verification email if not verified
    if not identity.email_verified:
        await _send_verification_email_task(user, background_tasks, settings, db)

    return RegisterInitResponse(
        user_id=user.id,
        email=identity.email,
        access_token=token,
        user=UserOut.model_validate(user),
        message="Registration initiated. Please verify your email." if not identity.email_verified else "Registration successful.",
        needs_company_setup=user.tenant_id is None,
        target_tenant_id=data.target_tenant_id,
    )


@router.post("/register/sso", response_model=TokenResponse)
async def register_sso(
    data: SSORegisterRequest,
    db: AsyncSession = Depends(get_db),
):
    """SSO registration - completely separate from normal registration flow.

    This endpoint handles OAuth-based registration/login via external providers.
    """
    from app.services.auth_registry import auth_provider_registry
    from app.services.registration_service import registration_service

    logger.info(f"[REGISTER_SSO] Starting SSO registration: provider={data.provider}")

    # Get provider
    auth_provider = await auth_provider_registry.get_provider(db, data.provider)
    if not auth_provider:
        raise HTTPException(status_code=400, detail=f"Provider '{data.provider}' not supported")

    # Perform SSO registration
    user, is_new, error = await registration_service.register_with_sso(
        db, data.provider, data.code, auth_provider
    )

    if error:
        raise HTTPException(status_code=400, detail=error)

    # If no tenant, check for email domain match
    if not user.tenant_id and user.email:
        tenant, _ = await registration_service.get_tenant_for_registration(
            db, email=user.email, invitation_code=data.invitation_code
        )
        if tenant:
            user.tenant_id = tenant.id
            await db.flush()

    # Generate token
    token = create_access_token(str(user.id), user.role)

    logger.info(f"[REGISTER_SSO] SSO successful: user_id={user.id}, is_new={is_new}")

    return TokenResponse(
        access_token=token,
        user=UserOut.model_validate(user),
        needs_company_setup=user.tenant_id is None,
    )


async def _handle_normal_register(data: UserRegister, background_tasks: BackgroundTasks, db: AsyncSession, settings):
    """Legacy normal registration handler."""
    logger.info(f"[REGISTER_LEGACY] email={data.email}")

    from app.services.registration_service import registration_service
    from sqlalchemy import func

    # Resolve email config once
    from app.services.system_email_service import resolve_email_config_async
    email_config = await resolve_email_config_async(db)

    # Check if first user - Optimize with EXISTS
    is_first_user = (await db.execute(select(User.id).limit(1))).scalar() is None

    # Resolve tenant
    tenant_uuid = None
    if is_first_user:
        from app.models.tenant import Tenant
        default = await db.execute(select(Tenant).where(Tenant.slug == "default"))
        tenant = default.scalar_one_or_none()
        if not tenant:
            tenant = Tenant(name="Default", slug="default", im_provider="web_only")
            db.add(tenant)
            await db.flush()
        tenant_uuid = tenant.id
        role = "platform_admin"
    else:
        tenant, _ = await registration_service.get_tenant_for_registration(
            db, email=data.email, invitation_code=data.invitation_code
        )
        if tenant:
            tenant_uuid = tenant.id
        role = "member"

    # 1. Check for existing Identity/Tenant-User
    from app.services.registration_service import registration_service
    
    # Check if this email is already registered globally
    identity_query = select(Identity).where(Identity.email == data.email)
    ident_res = await db.execute(identity_query)
    identity = ident_res.scalar_one_or_none()

    if identity:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="Email already registered, please login directly."
        )
    
    # 2. Uniqueness Check (Already handled by Identity lookup above, but let's be explicit for Phone if needed)
    # conflicts = await registration_service.check_duplicate_identity(db, email=data.email)
    # ...

    # 3. Resolve or create Identity
    # If it's the first user, we auto-verify (trusted admin)
    identity = await registration_service.find_or_create_identity(
        db,
        email=data.email,
        username=data.username,
        password=data.password,
        is_platform_admin=is_first_user,
        email_config=email_config,
    )

    # Defense-in-depth: verify the returned identity actually belongs to the
    # submitted email. Should be unreachable after the username-lookup fix, but
    # acts as a safety net against future regressions.
    if identity.email and identity.email != data.email:
        logger.warning(
            "[REGISTER_LEGACY] Identity email mismatch: submitted=%s returned=%s — rejecting",
            data.email,
            identity.email,
        )
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="Username already taken. Please choose a different username.",
        )
    
    if is_first_user:
        identity.email_verified = True
        identity.is_active = True
        await db.flush()

    # 4. Create Tenant User (Handles OrgMember binding and Participant creation)
    user = await registration_service.create_user_with_identity(
        db,
        identity=identity,
        display_name=data.display_name or data.username,
        role=role,
        tenant_id=tenant_uuid,
        registration_source="web",
        email_config=email_config,
    )

    # Seed default agents for first user
    if is_first_user:
        await db.commit()
        try:
            from app.services.agent_seeder import seed_default_agents
            await seed_default_agents()
        except Exception as e:
            logger.warning(f"Failed to seed default agents: {e}")

    # Send verification email only when the identity still needs it. If the
    # platform has no system email configured, registration_service auto-verifies
    # the identity so local/self-hosted installs are not blocked.
    if not identity.email_verified:
        await _send_verification_email_task(user, background_tasks, settings, db)

    return RegisterInitResponse(
        user_id=user.id,
        email=user.email,
        access_token=create_access_token(str(user.id), user.role),
        user=UserOut.model_validate(user),
        message="Registration successful. Please verify your email." if not identity.email_verified else "Registration successful.",
        needs_company_setup=user.tenant_id is None,
    )


async def _handle_sso_register(data: UserRegister, db: AsyncSession):
    """Legacy SSO registration handler - delegates to new SSO endpoint logic."""
    # Redirect to new SSO flow
    sso_data = SSORegisterRequest(
        provider=data.provider,
        code=data.provider_code,
        invitation_code=data.invitation_code
    )
    return await register_sso(sso_data, db)



@router.post("/login", response_model=Any)
async def login(data: UserLogin, background_tasks: BackgroundTasks, db: AsyncSession = Depends(get_db)):
    """Login with email/phone/username and password. Supports multi-tenant selection."""
    from app.models.tenant import Tenant
    from app.models.user import Identity, User

    # 1. Query Identity
    query = select(Identity).where(
        (Identity.email == data.login_identifier) |
        (Identity.phone == data.login_identifier) |
        (Identity.username == data.login_identifier)
    )
    result = await db.execute(query)
    identity = result.scalar_one_or_none()

    if not identity or not identity.password_hash or not await verify_password_async(data.password, identity.password_hash):
        logger.warning(f"[LOGIN] Invalid credentials for {data.login_identifier} identity_id={identity.id if identity else 'None'}")
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials")

    # 2. Check Global Activity & Verification
    if not identity.is_active:
         raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Your account has been disabled.")

    if not identity.email_verified:
        from app.config import get_settings
        from sqlalchemy import update
        from app.services.system_email_service import resolve_email_config_async

        email_config = await resolve_email_config_async(db)
        if not email_config:
            identity.email_verified = True
            identity.is_active = True
            await db.execute(
                update(User)
                .where(User.identity_id == identity.id)
                .values(is_active=True)
            )
            await db.flush()
        else:
            # Find any user record (just for the task)
            user_res = await db.execute(select(User).where(User.identity_id == identity.id).limit(1))
            user = user_res.scalar_one_or_none()
            
            # Trigger email delivery in background
            if user:
                await _send_verification_email_task(user, background_tasks, get_settings(), db)
            
            # Consistent with identity-first flow: Return 403 Forbidden with verification intent
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail={
                    "needs_verification": True,
                    "email": identity.email,
                    "message": "Please verify your email to continue."
                }
            )

    # 3. Find all User records (tenants)
    result = await db.execute(select(User).where(User.identity_id == identity.id).options(selectinload(User.identity)))
    valid_users = list(result.scalars().all())

    if not valid_users:
        # User has an identity but no tenant records? Should they create one?
        # Create a "tenant-less" user if needed, or redirect to company setup
        # For now, if no users, they need company setup.
        # But wait, register_init should have created one.
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="No organization associated with this account.")

    # 4. Handle Tenant Selection
    if not data.tenant_id:
        # If multiple tenants, return choice
        if len(valid_users) > 1:
            tenant_ids = [u.tenant_id for u in valid_users if u.tenant_id]
            tenants_map = {}
            if tenant_ids:
                tenants_result = await db.execute(
                    select(Tenant).where(Tenant.id.in_(tenant_ids))
                )
                tenants_map = {str(t.id): t for t in tenants_result.scalars().all()}

            tenant_choices = []
            for u in valid_users:
                tenant = tenants_map.get(str(u.tenant_id)) if u.tenant_id else None
                tenant_choices.append(TenantChoice(
                    tenant_id=u.tenant_id,
                    tenant_name=tenant.name if tenant else "Create or Join Organization",
                    tenant_slug=tenant.slug if tenant else "",
                    logo_url=tenant.logo_url if tenant else None,
                ))

            return MultiTenantResponse(
                requires_tenant_selection=True,
                login_identifier=data.login_identifier,
                tenants=tenant_choices,
            )

        # Only one tenant
        user = valid_users[0]
    else:
        # Specific tenant requested (Dedicated Link flow)
        # Search for the user record in that tenant
        user = next((u for u in valid_users if u.tenant_id == data.tenant_id), None)
        
        # Cross-tenant access check
        if not user:
             # Even platform admins must have a valid record in the targeted tenant 
             # when logging in via a dedicated tenant URL / tenant_id.
             raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="This account does not belong to the selected organization.",
            )


    if user.tenant_id:
        t_result = await db.execute(select(Tenant).where(Tenant.id == user.tenant_id))
        tenant = t_result.scalar_one_or_none()
        if tenant and not tenant.is_active:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="Your organization has been disabled.",
            )

    # 6. Generate Token
    token = create_access_token(str(user.id), user.role)
    return TokenResponse(
        access_token=token,
        user=UserOut.model_validate(user),
        identity=IdentityOut.model_validate(identity),
        needs_company_setup=user.tenant_id is None,
    )


@router.get("/email-hint")
async def get_email_hint(username: str, db: AsyncSession = Depends(get_db)):
    """Return a hinted email address for a given username."""
    from app.models.user import Identity
    result = await db.execute(select(Identity).where(Identity.username == username))
    identity = result.scalar_one_or_none()
    
    if not identity or not identity.email:
        raise HTTPException(status_code=404, detail="Account not found.")
        
    email = identity.email
    parts = email.split("@")
    if len(parts) == 2:
        name, domain = parts
        
        # Obfuscate name
        if len(name) <= 2:
            obs_name = name[0] + "***"
        else:
            obs_name = name[:2] + "***" + name[-1]
            
        # Obfuscate domain
        domain_parts = domain.split(".")
        if len(domain_parts) >= 2:
            d_name = domain_parts[0]
            d_ext = ".".join(domain_parts[1:])
            if len(d_name) <= 2:
                obs_domain = d_name[0] + "***." + d_ext
            else:
                obs_domain = d_name[0] + "***" + d_name[-1] + "." + d_ext
            hint = f"{obs_name}@{obs_domain}"
        else:
            hint = f"{obs_name}@{domain}"
    else:
        hint = email[:3] + "***"
        
    return {"hint": hint}


@router.post("/forgot-password")
async def forgot_password(
    data: ForgotPasswordRequest,
    background_tasks: BackgroundTasks,
    db: AsyncSession = Depends(get_db),
):
    """Request a password reset link for a global Identity."""
    from app.services.system_email_service import resolve_email_config_async
    email_config = await resolve_email_config_async(db)

    if not email_config:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Password reset is currently unavailable (no mail server configured)."
        )

    generic_response = {
        "ok": True,
        "message": "If an account with that email exists, a password reset email has been sent.",
    }

    # Find Identity by email
    identity_query = select(Identity).where(Identity.email == data.email)
    identity_result = await db.execute(identity_query)
    identity = identity_result.scalar_one_or_none()
    
    if not identity or not identity.is_active:
        return generic_response

    try:
        from app.services.password_reset_service import build_password_reset_url, create_password_reset_token
        from app.services.system_email_service import (
            send_password_reset_email,
        )

        raw_token, expires_at = await create_password_reset_token(identity.id)

        reset_url = await build_password_reset_url(db, raw_token)
        expiry_minutes = int((expires_at - datetime.now(timezone.utc)).total_seconds() // 60)
        background_tasks.add_task(
            send_password_reset_email,
            identity.email,
            identity.username or "User",
            reset_url,
            expiry_minutes,
        )
    except Exception as exc:
        logger.warning(f"Failed to process password reset email for {data.email}: {exc}")

    return generic_response


@router.post("/reset-password")
async def reset_password(data: ResetPasswordRequest, db: AsyncSession = Depends(get_db)):
    """Reset a password using a valid single-use token."""
    from app.services.password_reset_service import consume_password_reset_token

    token_data = await consume_password_reset_token(data.token)
    if not token_data:
        raise HTTPException(status_code=400, detail="Invalid or expired reset token")

    identity_id = token_data["identity_id"]
    result = await db.execute(select(Identity).where(Identity.id == identity_id))
    identity = result.scalar_one_or_none()
    
    if not identity or not identity.is_active:
        raise HTTPException(status_code=400, detail="Invalid or expired reset token")

    new_hash = await hash_password_async(data.new_password)
    identity.password_hash = new_hash

    await db.flush()
    await db.commit()
    return {"ok": True}


@router.get("/me", response_model=UserOut)
async def get_me(current_user: User = Depends(get_authenticated_user)):
    """Get current user profile."""
    data = UserOut.model_validate(current_user)
    data.is_platform_admin = bool(getattr(getattr(current_user, "identity", None), "is_platform_admin", False))
    return data


@router.patch("/me", response_model=UserOut)
async def update_me(
    data: UserUpdate,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """Update current user profile."""
    update_data = data.model_dump(exclude_unset=True)

    # Validate username uniqueness if changing
    if "username" in update_data and update_data["username"] != current_user.username:
        existing = await db.execute(
            select(User)
            .join(Identity, User.identity_id == Identity.id)
            .where(Identity.username == update_data["username"])
        )
        if existing.scalars().first():
            raise HTTPException(status_code=409, detail="Username already taken")

    # Validate email uniqueness within tenant if changing
    if "email" in update_data and update_data["email"] != current_user.email:
        existing = await db.execute(
            select(User)
            .join(Identity, User.identity_id == Identity.id)
            .where(
                Identity.email == update_data["email"],
                User.tenant_id == current_user.tenant_id,
                User.id != current_user.id,
            )
        )
        if existing.scalar_one_or_none():
            raise HTTPException(status_code=409, detail="Email already registered")

    # Validate mobile uniqueness within tenant if changing
    if "primary_mobile" in update_data and update_data["primary_mobile"] != current_user.primary_mobile:
        existing = await db.execute(
            select(User)
            .join(Identity, User.identity_id == Identity.id)
            .where(
                Identity.phone == update_data["primary_mobile"],
                User.tenant_id == current_user.tenant_id,
                User.id != current_user.id,
            )
        )
        if existing.scalar_one_or_none():
            raise HTTPException(status_code=409, detail="Mobile already registered")

    for field, value in update_data.items():
        setattr(current_user, field, value)
    await db.commit()
    await db.refresh(current_user)

    # Sync email/phone to OrgMember if changed
    if "email" in update_data or "primary_mobile" in update_data:
        from app.services.registration_service import registration_service
        await registration_service.sync_org_member_contact_from_user(
            db,
            current_user,
            sync_email="email" in update_data,
            sync_phone="primary_mobile" in update_data,
        )

    return UserOut.model_validate(current_user)


@router.get("/my-tenants", response_model=list[TenantChoice])
async def get_my_tenants(
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """Get all tenants associated with the current user's identity."""
    from app.models.tenant import Tenant

    # 1. Get all user records for this identity
    result = await db.execute(
        select(User).where(User.identity_id == current_user.identity_id)
    )
    users = result.scalars().all()

    # 2. Extract tenant IDs
    tenant_ids = [u.tenant_id for u in users if u.tenant_id]
    if not tenant_ids:
        return []

    # 3. Get tenant details
    result = await db.execute(
        select(Tenant).where(Tenant.id.in_(tenant_ids))
    )
    tenants = result.scalars().all()

    return [
        TenantChoice(
            tenant_id=t.id,
            tenant_name=t.name,
            tenant_slug=t.slug,
            logo_url=t.logo_url,
        ) for t in tenants
    ]


@router.post("/switch-tenant", response_model=TenantSwitchResponse)
async def switch_tenant(
    data: TenantSwitchRequest,
    request: Request,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """Switch to a different tenant and return a new token and redirect URL."""
    from app.models.tenant import Tenant
    from app.models.system_settings import SystemSetting

    # 1. Verify membership
    result = await db.execute(
        select(User).where(
            User.identity_id == current_user.identity_id,
            User.tenant_id == data.tenant_id
        )
    )
    target_user = result.scalar_one_or_none()

    if not target_user:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="You do not have access to this organization."
        )

    # 2. Get tenant details
    result = await db.execute(select(Tenant).where(Tenant.id == data.tenant_id))
    tenant = result.scalar_one_or_none()

    if not tenant or not tenant.is_active:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="This organization is currently unavailable."
        )

    # 3. Generate new token
    token = create_access_token(str(target_user.id), target_user.role)

    # 4. Determine redirect URL
    # Determine redirect URL (Priority: sso_domain > ENV > Request > Fallback)
    from app.services.platform_service import platform_service
    from app.models.system_settings import SystemSetting
    
    # Check if custom domain SSO redirect is enabled globally
    setting_result = await db.execute(
        select(SystemSetting).where(SystemSetting.key == "sso_custom_domain_redirect_enabled")
    )
    setting_s = setting_result.scalar_one_or_none()
    sso_redirect_enabled = setting_s.value.get("enabled", True) if setting_s else True

    if not sso_redirect_enabled:
        redirect_url = None
    else:
        redirect_url = await platform_service.get_tenant_sso_base_url(db, tenant, request)


    # Include token in redirect URL for cross-domain switching if needed
    if redirect_url:
        separator = "&" if "?" in redirect_url else "?"
        redirect_url = f"{redirect_url}{separator}token={token}"

    return TenantSwitchResponse(
        access_token=token,
        redirect_url=redirect_url,
        message="Switching organization..."
    )


@router.put("/me/password")
async def change_password(
    data: dict,
    current_user: User = Depends(get_authenticated_user),
    db: AsyncSession = Depends(get_db),
):
    """Change current user's password. Updates the global identity password."""
    old_password = data.get("old_password", "")
    new_password = data.get("new_password", "")

    if not old_password or not new_password:
        raise HTTPException(status_code=400, detail="Both old_password and new_password are required")

    if len(new_password) < 6:
        raise HTTPException(status_code=400, detail="New password must be at least 6 characters")

    # Access identity through current_user (TenantUser)
    res = await db.execute(select(User).where(User.id == current_user.id).options(selectinload(User.identity)))
    user = res.scalar_one()
    identity = user.identity

    if not identity or not identity.password_hash or not await verify_password_async(old_password, identity.password_hash):
        raise HTTPException(status_code=400, detail="Current password is incorrect")

    new_hash = await hash_password_async(new_password)
    identity.password_hash = new_hash
    
    await db.flush()
    await db.commit()
    return {"ok": True}


# ─── SSO/OAuth Endpoints ─────────────────────────────────────────────


@router.get("/providers")
async def list_providers(
    db: AsyncSession = Depends(get_db),
    tenant_id: uuid.UUID | None = Query(None, description="Optional tenant ID"),
):
    """List all available identity providers."""
    from app.services.auth_registry import auth_provider_registry

    providers = await auth_provider_registry.list_providers(db, str(tenant_id) if tenant_id else None)
    return [{"id": str(p.id), "provider_type": p.provider_type, "name": p.name, "is_active": p.is_active} for p in providers]


# Redis keys for OAuth two-step tenant selection
_OAUTH_PENDING_PREFIX = "oauth_pending:"
_OAUTH_PENDING_TTL = 600  # 10 minutes


async def _cache_oauth_pending(
    pending_token: str,
    provider_type: str,
    user_info_dict: dict,
    token_data: dict,
) -> None:
    """Store OAuth intermediate data in Redis for the two-step tenant-selection flow."""
    import json
    from app.core.events import get_redis
    r = await get_redis()
    payload = json.dumps({
        "provider_type": provider_type,
        "user_info": user_info_dict,
        "token_data": token_data,
    })
    await r.set(f"{_OAUTH_PENDING_PREFIX}{pending_token}", payload, ex=_OAUTH_PENDING_TTL)


async def _get_oauth_pending(pending_token: str) -> dict | None:
    """Retrieve (and delete) cached OAuth data from Redis. Returns None if expired/missing."""
    import json
    from app.core.events import get_redis
    r = await get_redis()
    raw = await r.get(f"{_OAUTH_PENDING_PREFIX}{pending_token}")
    if not raw:
        return None
    # Single-use: delete immediately after retrieval
    await r.delete(f"{_OAUTH_PENDING_PREFIX}{pending_token}")
    return json.loads(raw)


@router.get("/{provider}/authorize", response_model=OAuthAuthorizeResponse)
async def authorize(
    provider: str,
    redirect_uri: str = Query(..., description="OAuth callback URI"),
    state: str = Query("", description="CSRF state parameter"),
    db: AsyncSession = Depends(get_db),
):
    """Start OAuth authorization flow for a provider."""
    from app.services.auth_registry import auth_provider_registry
    from app.services.sso_service import sso_service

    # Get provider
    auth_provider = await auth_provider_registry.get_provider(db, provider)
    if not auth_provider:
        raise HTTPException(status_code=404, detail=f"Provider '{provider}' not supported")

    # Generate authorization URL
    try:
        auth_url = await auth_provider.get_authorization_url(redirect_uri, state)
    except NotImplementedError as e:
        raise HTTPException(status_code=501, detail=str(e))
    except Exception as e:
        logger.error(f"Failed to generate authorization URL for {provider}: {e}")
        raise HTTPException(status_code=500, detail="Failed to generate authorization URL")

    return OAuthAuthorizeResponse(authorization_url=auth_url)


@router.post("/{provider}/callback", response_model=Any)
async def oauth_callback(
    provider: str,
    data: OAuthCallbackRequest,
    db: AsyncSession = Depends(get_db),
):
    """Handle OAuth callback — supports a two-step flow for multi-tenant selection.

    Step 1 (code provided): exchange code with provider, detect multiple tenants,
    cache user_info in Redis, return MultiTenantResponse with opaque pending_token.

    Step 2 (pending_token + tenant_id provided): retrieve cached user_info from Redis,
    call find_or_create_user with the chosen tenant_id, return TokenResponse.
    """
    import uuid as _uuid
    from app.models.tenant import Tenant
    from app.services.auth_registry import auth_provider_registry

    # ── Step 2: User has selected a tenant ───────────────────────────────────
    if data.pending_token and data.tenant_id:
        pending = await _get_oauth_pending(data.pending_token)
        if not pending:
            raise HTTPException(
                status_code=400,
                detail="OAuth session expired or invalid. Please sign in again.",
            )

        auth_provider = await auth_provider_registry.get_provider(db, pending["provider_type"])
        if not auth_provider:
            raise HTTPException(
                status_code=404,
                detail=f"Provider '{pending['provider_type']}' not supported",
            )

        from app.services.auth_provider import ExternalUserInfo
        user_info = ExternalUserInfo(**pending["user_info"])

        user, _ = await auth_provider.find_or_create_user(db, user_info, tenant_id=data.tenant_id)
        if not user:
            raise HTTPException(status_code=500, detail="Failed to create user")
        if not user.is_active:
            raise HTTPException(status_code=403, detail="Account is disabled")

        jwt_token = create_access_token(str(user.id), user.role)
        return TokenResponse(
            access_token=jwt_token,
            user=UserOut.model_validate(user),
            needs_company_setup=user.tenant_id is None,
        )

    # ── Step 1: Exchange code, detect multi-tenant ────────────────────────────
    if not data.code:
        raise HTTPException(status_code=400, detail="Missing authorization code")

    auth_provider = await auth_provider_registry.get_provider(db, provider)
    if not auth_provider:
        raise HTTPException(status_code=404, detail=f"Provider '{provider}' not supported")

    try:
        token_data = await auth_provider.exchange_code_for_token(data.code, data.redirect_uri)
        access_token = token_data.get("access_token")
        if not access_token:
            raise HTTPException(status_code=400, detail="Failed to get access token from provider")

        user_info = await auth_provider.get_user_info(access_token)
        user, is_new = await auth_provider.find_or_create_user(db, user_info)

        if not user:
            raise HTTPException(status_code=500, detail="Failed to create user")
        if not user.is_active:
            raise HTTPException(status_code=403, detail="Account is disabled")

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"OAuth callback failed for {provider}: {e}")
        raise HTTPException(status_code=500, detail="OAuth authentication failed")

    # Check if this identity has multiple tenant memberships
    if user.identity_id:
        all_users_result = await db.execute(
            select(User).where(User.identity_id == user.identity_id)
        )
        all_users = list(all_users_result.scalars().all())
        tenant_users = [u for u in all_users if u.tenant_id is not None]

        if len(tenant_users) > 1:
            # Cache the full user_info in Redis so Step 2 can reconstruct it
            pending_token = _uuid.uuid4().hex
            await _cache_oauth_pending(
                pending_token,
                provider,
                {
                    "provider_type": user_info.provider_type,
                    "provider_union_id": user_info.provider_union_id,
                    "provider_user_id": user_info.provider_user_id,
                    "name": user_info.name,
                    "email": user_info.email,
                    "avatar_url": user_info.avatar_url,
                    "mobile": user_info.mobile,
                    "raw_data": user_info.raw_data,
                },
                token_data,
            )

            tenant_ids = [u.tenant_id for u in tenant_users]
            tenants_result = await db.execute(select(Tenant).where(Tenant.id.in_(tenant_ids)))
            tenants_map = {str(t.id): t for t in tenants_result.scalars().all()}

            tenant_choices = [
                TenantChoice(
                    tenant_id=u.tenant_id,
                    tenant_name=tenants_map[str(u.tenant_id)].name if str(u.tenant_id) in tenants_map else "Unknown",
                    tenant_slug=tenants_map[str(u.tenant_id)].slug if str(u.tenant_id) in tenants_map else "",
                    logo_url=tenants_map[str(u.tenant_id)].logo_url if str(u.tenant_id) in tenants_map else None,
                )
                for u in tenant_users
            ]

            return MultiTenantResponse(
                requires_tenant_selection=True,
                login_identifier=user_info.email or "",
                tenants=tenant_choices,
                pending_token=pending_token,
            )

    # Single tenant (or new user with no tenant yet) — issue token directly
    jwt_token = create_access_token(str(user.id), user.role)
    return TokenResponse(
        access_token=jwt_token,
        user=UserOut.model_validate(user),
        needs_company_setup=user.tenant_id is None,
    )


@router.post("/{provider}/bind", response_model=UserOut)
async def bind_identity(
    provider: str,
    data: IdentityBindRequest,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """Bind an external identity to the current user."""
    from app.services.auth_registry import auth_provider_registry
    from app.services.sso_service import sso_service

    # Get provider
    auth_provider = await auth_provider_registry.get_provider(db, provider)
    if not auth_provider:
        raise HTTPException(status_code=404, detail=f"Provider '{provider}' not supported")

    try:
        # Exchange code for token
        token_data = await auth_provider.exchange_code_for_token(data.code)
        access_token = token_data.get("access_token")
        if not access_token:
            raise HTTPException(status_code=400, detail="Failed to get access token from provider")

        # Get user info
        user_info = await auth_provider.get_user_info(access_token)

        # Check if identity is already linked to another user
        lookup_provider_user_id = user_info.provider_user_id
        existing_user = await sso_service.check_duplicate_identity(
            db,
            provider,
            lookup_provider_user_id,
            identity_data=user_info.raw_data,
        )
        if existing_user and existing_user.id != current_user.id:
            raise HTTPException(
                status_code=409,
                detail="This identity is already linked to another account",
            )

        # Link identity to current user
        await sso_service.link_identity(
            db,
            str(current_user.id),
            provider,
            lookup_provider_user_id,
            user_info.raw_data,
        )

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"Identity bind failed for {provider}: {e}")
        raise HTTPException(status_code=500, detail="Failed to bind identity")

    return UserOut.model_validate(current_user)


@router.post("/{provider}/unbind", response_model=UserOut)
async def unbind_identity(
    provider: str,
    data: IdentityUnbindRequest,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """Unlink an external identity from the current user."""
    from app.services.sso_service import sso_service

    # Unlink identity
    success = await sso_service.unlink_identity(db, str(current_user.id), provider)
    if not success:
        raise HTTPException(status_code=404, detail=f"No linked identity found for provider '{provider}'")

    return UserOut.model_validate(current_user)


# ─── Email Verification Endpoints ──────────────────────────────────────


@router.post("/verify-email")
async def verify_email(data: VerifyEmailRequest, db: AsyncSession = Depends(get_db)):
    """Verify email address using a token from the verification email.

    On success, returns user info and access token to allow immediate login.
    """
    from app.services.email_verification_service import email_verification_service

    token_data = await email_verification_service.consume_email_verification_token(data.token)
    if not token_data:
        raise HTTPException(status_code=400, detail="Invalid or expired verification token")

    identity_id = token_data.get("identity_id")
    if not identity_id:
        raise HTTPException(status_code=400, detail="Token does not contain identity information")
    
    # 1. Update Identity
    identity_result = await db.execute(select(Identity).where(Identity.id == identity_id))
    identity = identity_result.scalar_one_or_none()
    if not identity:
        raise HTTPException(status_code=400, detail="Identity not found")

    identity.email_verified = True
    identity.is_active = True
    
    # 2. Activate all linked User accounts
    # email_verified is a proxy to Identity, so only update physical is_active column
    from sqlalchemy import update
    await db.execute(
        update(User)
        .where(User.identity_id == identity.id)
        .values(is_active=True)
    )
    
    await db.flush()
    await db.commit()

    # Refresh after commit to avoid MissingGreenlet during Pydantic validation
    await db.refresh(identity)

    # 3. Find a representative user for the token (for immediate login)
    user_result = await db.execute(
        select(User)
        .where(User.identity_id == identity.id)
        .order_by(User.created_at.desc())
        .limit(1)
    )
    user = user_result.scalar_one_or_none()

    # 4. Generate token and return full response for Auto Login (TokenResponse)
    effective_id = str(user.id) if user else str(identity.id)
    effective_role = user.role if user else "user"
    token = create_access_token(effective_id, effective_role)

    return TokenResponse(
        access_token=token,
        user=UserOut.model_validate(user) if user else None,
        identity=IdentityOut.model_validate(identity),
        needs_company_setup=user.tenant_id is None if user else True,
    )


@router.post("/resend-verification")
async def resend_verification(
    data: ResendVerificationRequest,
    background_tasks: BackgroundTasks,
    db: AsyncSession = Depends(get_db),
):
    """Resend email verification link."""
    from app.config import get_settings
    from app.services.system_email_service import resolve_email_config_async

    # Always return success to prevent email enumeration
    generic_response = {
        "ok": True,
        "message": "If an account with that email exists, a verification email has been sent.",
    }
    settings = get_settings()

    # Check if email is configured (DB-only, no env fallback)
    email_config = await resolve_email_config_async(db)
    if not email_config:
        return generic_response

    # Find Identity by email
    id_result = await db.execute(select(Identity).where(Identity.email == data.email))
    identity = id_result.scalar_one_or_none()

    # Don't reveal if user exists or already verified
    if not identity or identity.email_verified:
        return generic_response

    # Pick a representative user context (e.g. latest one)
    u_result = await db.execute(
        select(User).where(User.identity_id == identity.id).order_by(User.created_at.desc()).limit(1)
    )
    user = u_result.scalar_one_or_none()
    
    if user:
        await _send_verification_email_task(user, background_tasks, settings, db)

    return generic_response
