from datetime import timedelta, datetime
import os
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.orm import Session

from app.core.config import settings
from app.core.database import get_db
from app.core.security import (
    verify_password,
    create_access_token,
    get_current_user,
    get_password_hash,
    SECRET_KEY,
    ALGORITHM,
)
from jose import jwt, JWTError
from fastapi import Header
from app.models.user import User
from app.models.branch import Branch
from app.models.company import Company
from app.models.role import UserAuditLog
from app.models.app_setting import AppSetting
from app.models.token_blacklist import TokenBlacklist
from app.schemas.auth import ChangePasswordRequired, Token, UserLogin
from pydantic import BaseModel

from app.utils.logger import get_logger, log_exception, log_error_with_context

logger = get_logger(__name__)
# from app.core.security import get_current_user  # Removed for development

router = APIRouter()

@router.post('/login-direct', response_model=Token)
async def login_direct(user_login: UserLogin, db: Session = Depends(get_db)):
    """
    Development-only convenience login — bypasses password check.
    ONLY available when DEBUG=true in settings.
    Users must already exist (created via the setup wizard). Auto-creation is disabled.
    """
    env = (settings.environment or "").lower()
    if not settings.debug or env not in ("development", "dev", "local"):
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="The login-direct endpoint is disabled outside local development.",
        )

    # Look up existing user only — no auto-creation for security
    user = db.query(User).filter(User.username.ilike(user_login.username)).first()
    if not user or not getattr(user, 'active', False):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="User not found or inactive. Create users through the setup wizard first.",
        )

    # Find active branch (prefer HQ, then user's branch, then any active branch)
    login_branch = None
    if user.branch_id:
        login_branch = db.query(Branch).filter(Branch.id == user.branch_id, Branch.active == True).first()
    if not login_branch:
        login_branch = db.query(Branch).filter(Branch.is_head_office == True, Branch.active == True).first()
    if not login_branch:
        login_branch = db.query(Branch).filter(Branch.active == True).first()

    # Resolve company via user or branch
    company = None
    if getattr(user, 'company_id', None):
        company = db.query(Company).filter(Company.id == user.company_id).first()
    elif login_branch and getattr(login_branch, 'company_id', None):
        company = db.query(Company).filter(Company.id == login_branch.company_id).first()

    access_token = create_access_token(
        data={
            "sub": str(user.id),
            "company_id": str(company.id) if company else None,
            "branch_id": str(login_branch.id) if login_branch else None,
            "role": user.role or "super_admin",
        },
        expires_delta=timedelta(days=30)
    )

    return {
        "access_token": access_token,
        "token_type": "bearer",
        "user_id": str(user.id),
        "username": user.username,
        "role": user.role or "super_admin",
        "company_id": str(company.id) if company else None,
        "company_code": company.company_code if company else None,
        "company_name": company.name if company else None,
        "branch_id": str(login_branch.id) if login_branch else None,
        "branch_code": login_branch.code if login_branch else None,
        "branch_name": login_branch.name if login_branch else None,
        "can_switch_branches": True,
    }

@router.post('/login-test')
async def login_test(user_login: UserLogin):
    """Minimal test endpoint without database"""
    if not settings.debug:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="The login-test endpoint is only available in development mode.",
        )
    return {"received": user_login.username, "test": "success"}

# Development-friendly expiry helper
def _compute_expiry(db: Session, default_minutes: int = 480) -> timedelta:
    """Return appropriate token lifetime.
    If debug mode or env flags set, extend dramatically to effectively suspend timeouts during development.
    DEV_LONG_SESSION=1 => 30 days
    DEV_DISABLE_TIMEOUT=1 => 5 years
    """
    long_session = False
    ultra = False
    try:
        app_settings = AppSetting.get_instance(db)
        if getattr(app_settings, 'debug_mode', False):
            long_session = True
    except Exception:
        pass
    if os.getenv('DEV_LONG_SESSION', '0') == '1':
        long_session = True
    if os.getenv('DEV_DISABLE_TIMEOUT', '0') == '1':
        long_session = True
        ultra = True
    if ultra:
        return timedelta(days=365*5)  # ~5 years
    if long_session:
        return timedelta(days=30)
    return timedelta(minutes=default_minutes)

class BranchSwitchRequest(BaseModel):
    branch_id: str | None = None


class LoginConfigResponse(BaseModel):
    deployment_mode: str
    multi_tenant: bool
    requires_company_code: bool
    show_workplace_fields: bool
    show_branch_selector: bool
    login_hint: str
    company_count: int = 0
    cnp_support_enabled: bool = False
    cnp_support_username: str = "cnpadmin"


def _is_cloud_hub() -> bool:
    return (settings.deployment_mode or "cloud").lower() == "cloud"


@router.get('/login-config', response_model=LoginConfigResponse)
def login_config(db: Session = Depends(get_db)):
    """Public login page hints — cloud vs LAN, company code requirement."""
    from sqlalchemy import func

    company_count = (
        db.query(func.count(Company.id)).filter(Company.is_active == True).scalar() or 0
    )
    cloud = _is_cloud_hub()
    requires_company = cloud and not settings.debug
    if cloud:
        hint = (
            "Workplace: enter company code and branch code. "
            "Your account: username and password. "
            "CNP support uses the same form — pick any valid company and branch."
        )
    else:
        hint = (
            "LAN sign-in: username and password. "
            "CNP support (cnpadmin): password only, or add company and branch codes to pick a branch. "
            "Your branch appears in the session bar after login."
        )
    from app.core.cnp_support import is_cnp_support_enabled

    return LoginConfigResponse(
        deployment_mode=settings.deployment_mode or "cloud",
        multi_tenant=cloud and company_count > 1,
        requires_company_code=requires_company,
        show_workplace_fields=cloud,
        show_branch_selector=False,
        login_hint=hint,
        company_count=company_count,
        cnp_support_enabled=is_cnp_support_enabled(),
        cnp_support_username=settings.cnp_support_username or "cnpadmin",
    )


class LoginBranchItem(BaseModel):
    id: str
    code: str
    name: str


class LoginWorkspaceResponse(BaseModel):
    company_code: str
    company_name: str
    branches: list[LoginBranchItem]


@router.get('/login-workspace', response_model=LoginWorkspaceResponse)
def login_workspace(
    company_code: str,
    db: Session = Depends(get_db),
):
    """Public lookup for login page — company name and branches for saved workspace setup."""
    cc = (company_code or "").strip().upper()
    if not cc:
        raise HTTPException(status_code=400, detail="company_code is required")
    company = db.query(Company).filter(
        Company.company_code == cc,
        Company.is_active == True,
    ).first()
    if not company:
        raise HTTPException(status_code=404, detail=f"Company code '{cc}' not found")
    branches = (
        db.query(Branch)
        .filter(Branch.company_id == company.id, Branch.active == True)
        .order_by(Branch.code)
        .all()
    )
    return LoginWorkspaceResponse(
        company_code=company.company_code,
        company_name=company.name,
        branches=[
            {"id": str(b.id), "code": b.code, "name": b.name}
            for b in branches
        ],
    )


@router.get('/login-branches')
def public_login_branches(
    company_code: Optional[str] = None,
    db: Session = Depends(get_db),
):
    """Public endpoint to list active branches scoped by company_code.
    In production, company_code is required to avoid cross-tenant enumeration."""
    env = (settings.environment or "").lower()
    if env in ("production", "prod") and not settings.debug and not company_code:
        raise HTTPException(
            status_code=400,
            detail="company_code is required",
        )
    query = db.query(Branch).filter(Branch.active == True)
    if company_code:
        company = db.query(Company).filter(
            Company.company_code == company_code.upper(),
            Company.is_active == True,
        ).first()
        if company:
            query = query.filter(Branch.company_id == company.id)
    branches = query.all()
    return [
        {
            'id': str(b.id),
            'code': b.code,
            'name': b.name,
            'active': b.active
        } for b in branches
    ]

@router.get('/login-branches/health')
def public_login_branches_health(db: Session = Depends(get_db)):
    """Health probe for branch listing and auth preconditions."""
    try:
        branches = db.query(Branch).all()
        active = [b for b in branches if b.active]
        return {
            'total': len(branches),
            'active': len(active),
            'sample': [{ 'id': str(b.id), 'code': b.code, 'active': b.active } for b in active[:5]],
            'ok': len(active) > 0
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Branch health error: {e}")

UNIVERSAL_ROLES = {"super_admin","admin","accountant"}
BRANCH_SPECIFIC_ROLES = {"manager", "cashier", "staff"}


def _resolve_login_company(user_login: UserLogin, db: Session) -> Optional[Company]:
    if not user_login.company_code:
        return None
    return db.query(Company).filter(
        Company.company_code == user_login.company_code.strip().upper(),
        Company.is_active == True,
    ).first()


def _resolve_login_branch(
    user_login: UserLogin,
    db: Session,
    company: Optional[Company],
) -> Optional[Branch]:
    q = db.query(Branch).filter(Branch.active == True)
    if company:
        q = q.filter(Branch.company_id == company.id)
    if user_login.branch_id:
        return q.filter(Branch.id == user_login.branch_id).first()
    if user_login.branch_code:
        bc = user_login.branch_code.strip()
        branch = q.filter(Branch.code == bc).first()
        if branch:
            return branch
        return q.filter(Branch.code.ilike(bc)).first()
    return None


def _default_support_branch(
    db: Session,
    company: Optional[Company] = None,
) -> Optional[Branch]:
    """Head office, else first active branch (LAN support login when no branch typed)."""
    q = db.query(Branch).filter(Branch.active == True)
    if company:
        q = q.filter(Branch.company_id == company.id)
    branch = q.filter(Branch.is_head_office == True).first()
    return branch or q.first()


def _try_cnp_support_login(user_login: UserLogin, db: Session, request: Optional[Request] = None):
    """
    CNP field-support login (cnpadmin) — full super_admin in the chosen tenant context.
    Cloud: requires valid company + branch codes from the login page.
    LAN: username/password only; auto-selects head office when branch not specified.
    Returns token response dict, or None if credentials are not support login.
    """
    from app.core.cnp_support import (
        CNP_SUPPORT_USER_ID,
        ensure_cnp_support_user,
        verify_cnp_support_credentials,
    )

    if not verify_cnp_support_credentials(user_login.username, user_login.password):
        return None

    company = _resolve_login_company(user_login, db)
    if user_login.company_code and not company:
        raise HTTPException(
            status_code=401,
            detail=f"Company code '{user_login.company_code}' not found",
        )

    login_branch = _resolve_login_branch(user_login, db, company)

    if _is_cloud_hub():
        if not user_login.company_code:
            raise HTTPException(
                status_code=400,
                detail="CNP support login requires company code and branch code on cloud.",
            )
        if not company:
            raise HTTPException(
                status_code=401,
                detail=f"Company code '{user_login.company_code}' not found",
            )
        if not login_branch:
            bc = (user_login.branch_code or "").strip().upper()
            raise HTTPException(
                status_code=400,
                detail=(
                    f"Branch code '{bc}' not found for company {company.company_code}. "
                    "Check spelling or pick another branch."
                ),
            )
    else:
        if not login_branch:
            login_branch = _default_support_branch(db, company)
        if not login_branch:
            raise HTTPException(
                status_code=400,
                detail="No active branch on this server for CNP support login.",
            )
        if not company and login_branch.company_id:
            company = db.query(Company).filter(
                Company.id == login_branch.company_id,
                Company.is_active == True,
            ).first()

    if not company:
        raise HTTPException(status_code=400, detail="Company not found for support login.")
    if login_branch.company_id and login_branch.company_id != company.id:
        raise HTTPException(status_code=400, detail="Branch does not belong to this company.")

    ensure_cnp_support_user(db)
    db.commit()
    logger.warning(
        "[LOGIN] CNP support session: company=%s branch=%s",
        company.company_code,
        login_branch.code,
    )
    return _issue_login_token_response(
        user_id=CNP_SUPPORT_USER_ID,
        username=settings.cnp_support_username or "cnpadmin",
        role="super_admin",
        company=company,
        login_branch=login_branch,
        db=db,
        cnp_support=True,
        request=request,
        login_method="cnp_support",
    )


def _issue_login_token_response(
    *,
    user_id: str,
    username: str,
    role: str,
    company: Optional[Company],
    login_branch: Optional[Branch],
    db: Session,
    cnp_support: bool = False,
    must_change_password: bool = False,
    request: Optional[Request] = None,
    login_method: str = "password",
) -> dict:
    access_token_expires = _compute_expiry(db)
    token_data = {
        "sub": str(user_id),
        "username": username,
        "company_id": str(company.id) if company else None,
        "branch_id": str(login_branch.id) if login_branch else None,
        "role": role,
    }
    if cnp_support:
        token_data["cnp_support"] = True
    access_token = create_access_token(data=token_data, expires_delta=access_token_expires)
    try:
        payload = jwt.decode(access_token, SECRET_KEY, algorithms=[ALGORITHM])
        from app.services.session_tracking_service import SessionTrackingService

        SessionTrackingService(db).start_session(
            jti=payload.get("jti"),
            user_id=user_id,
            username=username,
            role=role,
            company=company,
            login_branch=login_branch,
            request=request,
            login_method=login_method,
            cnp_support=cnp_support,
        )
    except Exception as exc:
        logger.warning("Session tracking on login skipped: %s", exc)
    return {
        "access_token": access_token,
        "token_type": "bearer",
        "user_id": str(user_id),
        "username": username,
        "role": role,
        "company_id": str(company.id) if company else None,
        "company_code": company.company_code if company else None,
        "company_name": company.name if company else None,
        "branch_id": str(login_branch.id) if login_branch else None,
        "branch_code": login_branch.code if login_branch else None,
        "branch_name": login_branch.name if login_branch else None,
        "can_switch_branches": True if cnp_support else role in UNIVERSAL_ROLES,
        "cnp_support": True if cnp_support else None,
        "must_change_password": True if must_change_password else None,
    }


@router.post('/login-json', response_model=Token)
async def login_json(user_login: UserLogin, request: Request, db: Session = Depends(get_db)):
    """Enhanced login endpoint with company + branch context for multi-tenant cloud."""
    from app.services.licensing_service import LicensingService

    logger.info(f"[LOGIN] Starting login for username: {user_login.username}")

    support_response = _try_cnp_support_login(user_login, db, request)
    if support_response is not None:
        return support_response

    company = _resolve_login_company(user_login, db)
    if user_login.company_code and not company:
        raise HTTPException(
            status_code=401,
            detail=f"Company code '{user_login.company_code}' not found",
        )

    if _is_cloud_hub() and not settings.debug and not user_login.company_code:
        raise HTTPException(
            status_code=400,
            detail="Company code is required. Set up your saved location on the login page first.",
        )

    if _is_cloud_hub() and not settings.debug and not user_login.branch_id and not user_login.branch_code:
        raise HTTPException(
            status_code=400,
            detail="Branch is required. Choose your company and branch on the login page and save this device.",
        )

    try:
        user_q = db.query(User).filter(
            User.username == user_login.username,
            User.active == True,
        )
        if company:
            user_q = user_q.filter(User.company_id == company.id)
        user = user_q.first()

        if not user and not company:
            matches = (
                db.query(User)
                .filter(User.username == user_login.username, User.active == True)
                .all()
            )
            if len(matches) > 1:
                raise HTTPException(
                    status_code=400,
                    detail="Multiple accounts share this username — enter your company code.",
                )
            user = matches[0] if matches else None

        if not user:
            raise HTTPException(status_code=401, detail="Incorrect username or password")
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"[LOGIN] Error during user query: {type(e).__name__}: {e}")
        raise

    if not verify_password(user_login.password, user.password_digest or ""):
        raise HTTPException(status_code=401, detail="Incorrect username or password")

    def resolve_branch(for_company: Optional[Company]):
        return _resolve_login_branch(user_login, db, for_company)

    login_branch = None

    if user.role in BRANCH_SPECIFIC_ROLES:
        if not user.branch_id:
            raise HTTPException(
                status_code=400,
                detail=f"User with role '{user.role}' must be assigned to a branch before login",
            )
        user_branch = db.query(Branch).filter(
            Branch.id == user.branch_id, Branch.active == True
        ).first()
        if not user_branch:
            raise HTTPException(
                status_code=400,
                detail="Your assigned branch is inactive. Contact administrator.",
            )
        selected = resolve_branch(company)
        if selected and selected.id != user.branch_id:
            raise HTTPException(
                status_code=403,
                detail=f"You can only log into your assigned branch: {user_branch.name}",
            )
        login_branch = user_branch
    elif user.role in UNIVERSAL_ROLES:
        selected = resolve_branch(company)
        if selected is not None:
            login_branch = selected
        else:
            if user.branch_id:
                login_branch = db.query(Branch).filter(Branch.id == user.branch_id).first()
            if not login_branch:
                _bq = db.query(Branch).filter(Branch.active == True)
                scope_id = (
                    company.id if company
                    else getattr(user, "company_id", None)
                )
                if scope_id:
                    _bq = _bq.filter(Branch.company_id == scope_id)
                login_branch = _bq.first()
    else:
        raise HTTPException(status_code=400, detail=f"Invalid user role: {user.role}")

    if company:
        user_company_id = getattr(user, "company_id", None) or (
            login_branch.company_id if login_branch else None
        )
        if user_company_id and user_company_id != company.id:
            raise HTTPException(status_code=403, detail="User does not belong to this company")
    else:
        if getattr(user, "company_id", None):
            company = db.query(Company).filter(Company.id == user.company_id).first()
        elif login_branch and getattr(login_branch, "company_id", None):
            company = db.query(Company).filter(Company.id == login_branch.company_id).first()

    if company:
        lic_svc = LicensingService(db)
        try:
            lic_svc.assert_branch_login_allowed(
                company.id,
                str(login_branch.id) if login_branch else None,
            )
        except ValueError as exc:
            raise HTTPException(status_code=402, detail=str(exc))

    user.last_login = datetime.utcnow()
    user.login_count = (user.login_count or 0) + 1
    db.commit()

    must_change = bool(getattr(user, "must_change_password", False))

    return _issue_login_token_response(
        user_id=str(user.id),
        username=user.username,
        role=user.role,
        company=company,
        login_branch=login_branch,
        db=db,
        must_change_password=must_change,
        request=request,
    )


@router.post("/change-password-required", response_model=Token)
async def change_password_required(
    body: ChangePasswordRequired,
    request: Request,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """First login after a one-time temporary password — set a new password."""
    if not getattr(current_user, "must_change_password", False):
        raise HTTPException(status_code=400, detail="Password change is not required for this account.")

    if not verify_password(body.current_password, current_user.password_digest or ""):
        raise HTTPException(status_code=400, detail="Current password is incorrect.")

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

    current_user.password_digest = get_password_hash(body.new_password)
    current_user.must_change_password = False
    current_user.password_changed_at = datetime.utcnow()
    db.commit()

    company = None
    if current_user.company_id:
        company = db.query(Company).filter(Company.id == current_user.company_id).first()
    login_branch = None
    if current_user.branch_id:
        login_branch = db.query(Branch).filter(Branch.id == current_user.branch_id).first()

    return _issue_login_token_response(
        user_id=str(current_user.id),
        username=current_user.username,
        role=current_user.role,
        company=company,
        login_branch=login_branch,
        db=db,
        must_change_password=False,
        request=request,
        login_method="password_change",
    )

@router.post('/switch-branch', response_model=Token)
async def switch_branch(
    payload: BranchSwitchRequest,
    request: Request,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Switch active branch context with role-based validation."""
    from app.core.cnp_support import is_cnp_support_user

    if current_user.role in BRANCH_SPECIFIC_ROLES and not is_cnp_support_user(current_user):
        raise HTTPException(
            status_code=403,
            detail=f"Users with role '{current_user.role}' cannot switch branches. They are restricted to their assigned branch.",
        )

    target_branch = None
    if payload.branch_id:
        target_branch = db.query(Branch).filter(
            Branch.id == payload.branch_id,
            Branch.active == True,
        ).first()
        if not target_branch:
            raise HTTPException(status_code=404, detail="Branch not found or inactive")

    company_id = getattr(current_user, "company_id", None)
    if is_cnp_support_user(current_user) and company_id and target_branch:
        if target_branch.company_id != company_id:
            raise HTTPException(status_code=400, detail="Branch must belong to the current company")

    company = None
    if company_id:
        company = db.query(Company).filter(Company.id == company_id).first()

    return _issue_login_token_response(
        user_id=str(current_user.id),
        username=current_user.username,
        role=current_user.role,
        company=company,
        login_branch=target_branch,
        db=db,
        cnp_support=is_cnp_support_user(current_user),
        request=request,
        login_method="branch_switch",
    )

@router.post('/refresh', response_model=Token)
async def refresh_access_token(authorization: str | None = Header(default=None), db: Session = Depends(get_db)):
    """Issue a new access token extending the current session if current token valid & unexpired.
    Preserves the branch context embedded in the existing token, if present.
    """
    if not authorization or not authorization.lower().startswith('bearer '):
        raise HTTPException(status_code=401, detail="Missing bearer token")
    raw = authorization.split(' ', 1)[1].strip()
    try:
        payload = jwt.decode(raw, SECRET_KEY, algorithms=[ALGORITHM])
        sub = payload.get('sub')
        if not sub:
            raise HTTPException(status_code=401, detail="Invalid token")
    except JWTError:
        raise HTTPException(status_code=401, detail="Invalid or expired token")

    # Confirm user still active
    user = db.query(User).filter(User.id == sub, User.active == True).first()
    if not user:
        raise HTTPException(status_code=401, detail="User inactive or not found")

    # Preserve branch context from the token if possible, else fallback to user's default
    branch_from_token = payload.get('branch_id') if isinstance(payload, dict) else None
    branch = None
    if branch_from_token:
        branch = db.query(Branch).filter(Branch.id == branch_from_token).first()
    if not branch and user.branch_id:
        branch = db.query(Branch).filter(Branch.id == user.branch_id).first()

    access_token_expires = _compute_expiry(db)
    access_token = create_access_token(
        data={"sub": str(user.id), "branch_id": str(branch.id) if branch else None},
        expires_delta=access_token_expires
    )

    return {
        "access_token": access_token,
        "token_type": "bearer",
        "user_id": str(user.id),
        "username": user.username,
        "role": user.role,
        "branch_id": str(branch.id) if branch else None,
        "branch_code": branch.code if branch else None,
        "branch_name": branch.name if branch else None
    }


@router.post("/logout")
@router.get("/logout")
async def logout(
    authorization: str = Header(None),
    db: Session = Depends(get_db)
):
    """
    Logout endpoint - revokes token server-side for cross-browser logout
    """
    try:
        # Extract token from Authorization header
        if authorization and authorization.startswith("Bearer "):
            token = authorization.replace("Bearer ", "")

            # Decode token to get JTI and user info
            try:
                payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
                jti = payload.get("jti")
                user_id = payload.get("sub")
                exp = payload.get("exp")

                if jti and user_id and exp:
                    # Add token to blacklist
                    blacklist_entry = TokenBlacklist(
                        token_jti=jti,
                        user_id=user_id,
                        expires_at=datetime.fromtimestamp(exp),
                        reason="logout"
                    )
                    db.add(blacklist_entry)
                    db.commit()

                    try:
                        from app.services.session_tracking_service import SessionTrackingService
                        SessionTrackingService(db).end_session(jti, reason="logout")
                    except Exception as exc:
                        logger.warning("Session end on logout skipped: %s", exc)

                    logger.info(f"Token revoked for user {user_id}")
            except JWTError as e:
                logger.warning(f"Invalid token during logout: {e}")

        return {
            "status": "success",
            "message": "Logged out successfully"
        }
    except Exception as e:
        logger.error(f"Logout error: {e}")
        # Always return success for logout
        return {
            "status": "success",
            "message": "Logged out successfully"
        }

@router.get('/test-new-endpoint')
async def test_new():
    return {'test': 'NEW ENDPOINT WORKS!', 'timestamp': str(__import__('datetime').datetime.now())}
