"""Shared authorization functions for user-scoped access control.

All route files use these functions to enforce project-level access control.
API key auth is always unrestricted (system/admin level).
"""

from __future__ import annotations

import logging

from fastapi import HTTPException, Request

from peon_mcp.common.auth import AuthInfo

logger = logging.getLogger(__name__)


async def authorize_project_access(
    db,
    auth: AuthInfo,
    project_id: str,
    request: Request,
) -> dict:
    """Fetch a project and verify the caller has access.

    Returns the project dict on success, raises 404 if the project doesn't
    exist or the user doesn't have access (404 to avoid leaking existence).

    Authorization rules for session auth:
    1. User owns the project (owner_id = user_id) → allow
    2. Project's GitHub org matches one of the user's installations → allow
    3. Otherwise → 404

    API key / no auth → always allow.
    """
    rows = await db.execute_fetchall(
        "SELECT * FROM projects WHERE id = ?", (project_id,)
    )
    if not rows:
        raise HTTPException(404, detail="Project not found")

    from peon_mcp.db import row_to_dict
    project = row_to_dict(rows[0])

    # API key or no auth method → unrestricted
    if not auth.is_session:
        return project

    # Session auth → check ownership or org membership
    user_id = auth.user_id
    if user_id is None:
        raise HTTPException(404, detail="Project not found")

    # 1. User owns the project
    if project.get("owner_id") == user_id:
        return project

    # 2. Project's GitHub org matches user's installations
    org_logins = await _get_user_org_logins(db, auth, request)
    project_owner = _parse_project_github_owner(project)
    if project_owner and project_owner.lower() in org_logins:
        return project

    # 3. No access
    raise HTTPException(404, detail="Project not found")


async def get_accessible_project_ids(
    db,
    auth: AuthInfo,
    request: Request,
) -> list[str] | None:
    """Return project IDs the user can access, or None for unrestricted.

    Returns None for API key auth (no filtering needed).
    Returns a list of accessible project IDs for session auth.
    """
    if not auth.is_session:
        return None  # Unrestricted

    user_id = auth.user_id
    if user_id is None:
        return []

    org_logins = await _get_user_org_logins(db, auth, request)

    # Build query: owned projects + org-matched projects
    conditions = ["p.owner_id = ?"]
    params: list = [user_id]

    for org in org_logins:
        conditions.append("p.github_url ILIKE ?")
        params.append(f"%github.com/{org}/%")

    where = " OR ".join(conditions)
    rows = await db.execute_fetchall(
        f"SELECT p.id FROM projects p WHERE {where}",
        params,
    )
    return [r["id"] for r in rows]


async def authorize_entity_project_access(
    db,
    auth: AuthInfo,
    request: Request,
    table: str,
    entity_id: int,
    id_column: str = "id",
    project_id_column: str = "project_id",
) -> dict:
    """Authorize access to an entity by looking up its project_id.

    Used for endpoints like GET /api/features/{id} where the project_id
    is not in the URL path.

    Returns the entity row dict. Raises 404 if not found or not accessible.
    """
    from peon_mcp.db import row_to_dict

    rows = await db.execute_fetchall(
        f"SELECT * FROM {table} WHERE {id_column} = ?", (entity_id,)
    )
    if not rows:
        raise HTTPException(404, detail=f"{table.rstrip('s').capitalize()} not found")

    entity = row_to_dict(rows[0])
    project_id = entity.get(project_id_column)

    if project_id and auth.is_session:
        await authorize_project_access(db, auth, project_id, request)

    return entity


def _parse_project_github_owner(project: dict) -> str | None:
    """Extract the GitHub owner from a project's github_url."""
    from peon_mcp.github_app import _parse_github_owner

    github_url = project.get("github_url", "")
    return _parse_github_owner(github_url)


async def _get_user_org_logins(
    db,
    auth: AuthInfo,
    request: Request,
) -> list[str]:
    """Get the org logins the user has access to (cached)."""
    from peon_mcp.common.installation_cache import get_user_org_logins

    user_id = auth.user_id
    if user_id is None:
        return []

    session_token = request.cookies.get("peon_session")
    if not session_token:
        return []

    # Ensure token is valid (refreshes if expired)
    from peon_mcp.auth.tokens import ensure_valid_token

    encrypted_token = await ensure_valid_token(db, session_token)
    if not encrypted_token:
        return []

    return await get_user_org_logins(db, user_id, encrypted_token)
