"""ManageBac authentication helpers.

This module contains the low-level login and logout logic used by the
``managebac-fetch`` CLI package.

The public API is intentionally small:

- ``login`` authenticates against ManageBac and returns an authenticated
  cookie jar suitable for subsequent requests.
- ``logout`` attempts to end the ManageBac session cleanly.

The implementation validates login by checking that the authenticated session
can access the student dashboard without being redirected back to the login
page.
"""

from __future__ import annotations

from http.cookiejar import CookieJar
from urllib.parse import urljoin

import requests
from bs4 import BeautifulSoup

DEFAULT_TIMEOUT = 30
BASE_HEADERS = {
    "User-Agent": (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 (KHTML, like Gecko) "
        "Chrome/120.0.0.0 Safari/537.36"
    ),
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.9",
}


class ManageBacAuthenticationError(RuntimeError):
    """Raised when ManageBac authentication fails."""


def login(school_code: str, username: str, password: str) -> CookieJar:
    """Authenticate with ManageBac and return an authenticated cookie jar.

    Args:
        school_code: The ManageBac school subdomain.
        username: The ManageBac login email address.
        password: The ManageBac login password.

    Returns:
        An authenticated cookie jar for follow-up requests.

    Raises:
        ValueError: If required inputs are missing.
        ManageBacAuthenticationError: If login fails or cannot be validated.
        requests.RequestException: If an HTTP request fails.
    """
    _validate_credentials(school_code, username, password)

    session = requests.Session()
    session.headers.update(BASE_HEADERS)

    login_url = _absolute_url(school_code, "/login")
    sessions_url = _absolute_url(school_code, "/sessions")

    login_response = session.get(
        login_url,
        timeout=DEFAULT_TIMEOUT,
        allow_redirects=True,
    )
    login_response.raise_for_status()

    csrf_token = _extract_csrf_token(login_response.text)
    if not csrf_token:
        raise ManageBacAuthenticationError(
            "Could not find the login CSRF token on the ManageBac login page."
        )

    auth_response = session.post(
        sessions_url,
        data={
            "authenticity_token": csrf_token,
            "login": username,
            "password": password,
            "commit": "Sign-In",
        },
        timeout=DEFAULT_TIMEOUT,
        allow_redirects=True,
    )
    auth_response.raise_for_status()

    _validate_logged_in_session(session, school_code)

    return session.cookies


def logout(school_code: str, cookies: CookieJar | None) -> bool:
    """Attempt to log out from ManageBac.

    Args:
        school_code: The ManageBac school subdomain.
        cookies: The authenticated cookie jar.

    Returns:
        True if the logout request succeeded, otherwise False.
    """
    if not school_code or cookies is None:
        return False

    try:
        response = requests.get(
            _absolute_url(school_code, "/logout"),
            cookies=cookies,
            headers=BASE_HEADERS,
            timeout=DEFAULT_TIMEOUT,
            allow_redirects=True,
        )
        return response.ok
    except requests.RequestException:
        return False


def _validate_credentials(school_code: str, username: str, password: str) -> None:
    """Validate required authentication inputs."""
    if not school_code or not school_code.strip():
        raise ValueError("school_code must be a non-empty string.")
    if not username or not username.strip():
        raise ValueError("username must be a non-empty string.")
    if not password:
        raise ValueError("password must be a non-empty string.")


def _extract_csrf_token(html: str) -> str | None:
    """Extract the CSRF token from the login page HTML."""
    soup = BeautifulSoup(html, features="lxml")

    meta_tag = soup.find("meta", attrs={"name": "csrf-token"})
    if meta_tag:
        content = meta_tag.get("content")
        if isinstance(content, str) and content:
            return content

    hidden_input = soup.find("input", attrs={"name": "authenticity_token"})
    if hidden_input:
        value = hidden_input.get("value")
        if isinstance(value, str) and value:
            return value

    return None


def _validate_logged_in_session(session: requests.Session, school_code: str) -> None:
    """Verify that the session is authenticated by checking the student page."""
    student_url = _absolute_url(school_code, "/student")
    response = session.get(
        student_url,
        timeout=DEFAULT_TIMEOUT,
        allow_redirects=True,
    )
    response.raise_for_status()

    final_url = response.url.lower()
    html = response.text.lower()

    if "/login" in final_url or "/sessions" in final_url:
        raise ManageBacAuthenticationError(
            "Login failed: ManageBac redirected back to the login page."
        )

    if 'name="password"' in html and 'name="login"' in html:
        raise ManageBacAuthenticationError(
            "Login failed: credentials were not accepted."
        )

    if "controller-student" not in html and "/student" not in final_url:
        raise ManageBacAuthenticationError(
            "Login could not be validated from the student dashboard response."
        )


def _absolute_url(school_code: str, path: str) -> str:
    """Build an absolute ManageBac URL from a school code and path."""
    base_url = f"https://{school_code.strip()}.managebac.com"
    return urljoin(base_url, path)
