"""Authentication management for HLA-Compass SDK"""

import base64
import html as html_module
import http.server
import json
import logging
import os
import platform
import secrets
import socketserver
import time
import uuid
import webbrowser
from contextlib import contextmanager
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any, Dict, Optional
from urllib.parse import parse_qs, urlencode, urlparse

import requests
from cryptography.fernet import Fernet

from ._version import __version__ as SDK_VERSION
from .config import Config
from .utils import parse_api_error

# try:  # pragma: no cover - optional dependency
#     import keyring  # type: ignore
#     from keyring.errors import KeyringError  # type: ignore
# except Exception:  # pragma: no cover - gracefully degrade when unavailable
keyring = None


class KeyringError(Exception):
    pass


logger = logging.getLogger(__name__)


class AuthError(Exception):
    """Authentication error"""

    pass


class Auth:
    """Handle authentication with HLA-Compass API"""

    def __init__(self, environment: Optional[str] = None):
        """Initialize auth manager"""
        self._credentials_cache = None
        self._cache_expiry: float = 0.0  # monotonic deadline
        self.config = Config()
        self.credentials_path = self.config.get_credentials_path()
        self.session = requests.Session()
        ua = (
            f"hla-compass-sdk/{SDK_VERSION} "
            f"python/{platform.python_version()} "
            f"os/{platform.system()}-{platform.release()}"
        )
        self.session.headers.update(
            {
                "Accept": "application/json",
                "User-Agent": ua,
            }
        )
        self._keyring_service = "hla-compass-sdk"
        self._environment_override = Config._normalize_environment_name(environment) if environment else None

    def _current_environment(self) -> str:
        if self._environment_override:
            return self._environment_override
        return self.config.get_environment()

    def _api_endpoint(self) -> str:
        return Config.get_api_endpoint_for_environment(self._current_environment())

    def _console_endpoint(self) -> str:
        return Config.get_console_endpoint_for_environment(self._current_environment())

    def _json_headers(self) -> Dict[str, str]:
        """Default JSON headers with a unique request id"""
        headers = {key: str(value) for key, value in self.session.headers.items()}
        headers["Content-Type"] = "application/json"
        headers["X-Request-Id"] = str(uuid.uuid4())
        headers["X-HLA-Auth-Client"] = "cli"
        headers["X-Client-Type"] = "cli"
        headers["X-Client-Platform"] = "python-sdk"
        # Optional global correlation id
        try:
            corr = self.config.get_correlation_id()
            if corr:
                headers["X-Correlation-Id"] = corr
        except Exception:
            pass
        return headers

    def _invalidate_cache(self):
        """Invalidate credential cache"""
        self._credentials_cache = None
        self._cache_expiry = 0.0
        logger.debug("Auth cache invalidated")

    def _extract_error_message(self, data: Any, fallback: str) -> str:
        if isinstance(data, dict):
            error = data.get("error")
            if isinstance(error, dict):
                message = error.get("message")
                if isinstance(message, str) and message:
                    return message
            elif isinstance(error, str) and error:
                return error

            message = data.get("message")
            if isinstance(message, str) and message:
                return message
        return fallback

    def _extract_auth_payload(self, data: Any, fallback: str) -> Dict[str, Any]:
        if isinstance(data, dict):
            if "access_token" in data:
                return data

            payload = data.get("data")
            if data.get("success") and isinstance(payload, dict) and "access_token" in payload:
                return payload

        raise AuthError(self._extract_error_message(data, fallback))

    def _extract_success_payload(self, data: Any, fallback: str) -> Dict[str, Any]:
        if isinstance(data, dict):
            payload = data.get("data")
            if "success" in data or "data" in data:
                if data.get("success") and isinstance(payload, dict):
                    return payload
                raise AuthError(self._extract_error_message(data, fallback))

            if "error" in data:
                raise AuthError(self._extract_error_message(data, fallback))

            return data

        raise AuthError(fallback)

    @contextmanager
    def _temporary_environment(self, environment: Optional[str]):
        previous = self._environment_override
        if environment:
            self._environment_override = Config._normalize_environment_name(environment)
        try:
            yield
        finally:
            self._environment_override = previous

    def login(self, email: str, password: str, environment: Optional[str] = None) -> Dict[str, Any]:
        """
        Login to HLA-Compass API

        Args:
            email: User email
            password: User password
            environment: Target environment (dev/staging/prod)

        Returns:
            Authentication response with tokens

        Raises:
            AuthError: If login fails
        """
        with self._temporary_environment(environment):
            endpoint = f"{self._api_endpoint()}/v1/auth/login"

            try:
                response = self.session.post(
                    endpoint,
                    json={"email": email, "password": password},
                    headers=self._json_headers(),
                    timeout=30,
                )

                if response.status_code == 200:
                    payload = self._extract_auth_payload(response.json(), "Login failed")
                    self._save_credentials(payload)
                    self._invalidate_cache()
                    return payload

                # Handle non-JSON error responses gracefully
                raise AuthError(parse_api_error(response, "Login failed"))

            except requests.RequestException as exc:
                raise AuthError("Network error during login") from exc

    def developer_register(self, email: str, name: str) -> Dict[str, Any]:
        """
        Register as a developer

        Args:
            email: Developer email
            name: Developer name

        Returns:
            Registration response with temporary credentials

        Raises:
            AuthError: If registration fails
        """
        endpoint = f"{self._api_endpoint()}/v1/auth/developer-register"

        try:
            response = self.session.post(
                endpoint,
                json={"email": email, "name": name},
                headers=self._json_headers(),
                timeout=30,
            )

            if response.status_code == 200:
                return self._extract_success_payload(response.json(), "Registration failed")
            else:
                # Handle non-JSON error responses gracefully
                raise AuthError(parse_api_error(response, "Registration failed"))

        except requests.RequestException as exc:
            raise AuthError(f"Network error during registration: {exc}") from exc

    def register(
        self,
        email: str,
        name: str,
        environment: Optional[str] = None,
        **kwargs,
    ) -> bool:
        """
        Register a new user account

        Args:
            email: User email
            name: User's full name
            environment: Target environment (dev/staging/prod)

        Returns:
            True if registration is successful

        Raises:
            AuthError: If registration fails
        """
        with self._temporary_environment(environment):
            # For now, use the developer registration endpoint
            try:
                result = self.developer_register(email, name)
                if result:
                    return True
                return False
            except AuthError:
                raise
            except Exception as exc:
                raise AuthError(f"Registration failed: {exc}") from exc

    def login_browser(self, environment: Optional[str] = None) -> Dict[str, Any]:
        """
        Login via browser-based SSO flow (PKCE Loopback).

        Spins up a local HTTP server, opens the system browser to the platform
        login page, and waits for a callback with credentials.

        Args:
            environment: Target environment (dev/staging/prod)

        Returns:
            Authentication response with tokens
        """
        with self._temporary_environment(environment):
            # 1. Setup local server
            # Using port 0 allows OS to pick a free port
            # However, for the redirect_uri to be whitelistable in some IDPs, fixed
            # ports are better. We'll try a few standard ports or let the platform
            # handle dynamic ports if supported.
            # Assuming dynamic ports are supported by the /cli-login handler.

            auth_result = {}
            auth_error = None
            expected_state = secrets.token_urlsafe(32)

            class CallbackHandler(http.server.BaseHTTPRequestHandler):
                def do_GET(self):
                    nonlocal auth_result, auth_error
                    try:
                        # Parse URL
                        parsed = urlparse(self.path)
                        if parsed.path != "/cli-callback":
                            self.send_error(404, "Not Found")
                            return

                        params = parse_qs(parsed.query)
                        callback_state = params.get("state", [None])[0]
                        if callback_state != expected_state:
                            auth_error = "Invalid state parameter"
                            self._send_response("Login failed: invalid state.", status=400)
                            return

                        # Expecting 'payload' (base64 json) or 'error'
                        if "error" in params:
                            auth_error = params["error"][0]
                            self._send_response("Login failed: " + auth_error)
                            return

                        if "payload" in params:
                            self._process_payload(params["payload"][0])
                        else:
                            self._send_fragment_handoff_page()
                    except Exception as e:
                        auth_error = str(e)

                def do_POST(self):
                    nonlocal auth_result, auth_error
                    try:
                        parsed = urlparse(self.path)
                        if parsed.path != "/cli-callback":
                            self.send_error(404, "Not Found")
                            return

                        content_length = int(self.headers.get("Content-Length", "0"))
                        body = self.rfile.read(content_length) if content_length > 0 else b"{}"

                        try:
                            data = json.loads(body.decode("utf-8"))
                        except Exception as exc:
                            auth_error = f"Invalid callback payload: {exc}"
                            self._send_response("Error processing login callback.", status=400)
                            return

                        if not isinstance(data, dict):
                            auth_error = "Invalid callback payload type"
                            self._send_response("Error processing login callback.", status=400)
                            return

                        callback_state = data.get("state")
                        if callback_state != expected_state:
                            auth_error = "Invalid state parameter"
                            self._send_response("Login failed: invalid state.", status=400)
                            return

                        callback_error = data.get("error")
                        if isinstance(callback_error, str) and callback_error.strip():
                            auth_error = callback_error
                            self._send_response("Login failed: " + auth_error)
                            return

                        payload = data.get("payload")
                        if not isinstance(payload, str) or not payload:
                            auth_error = "Missing payload in callback"
                            self._send_response("Missing credentials in callback.", status=400)
                            return

                        self._process_payload(payload)
                    except Exception as exc:
                        auth_error = str(exc)
                        self._send_response("Error processing login callback.", status=400)

                def _process_payload(self, encoded_payload: str):
                    nonlocal auth_result, auth_error
                    try:
                        payload_json = base64.b64decode(encoded_payload).decode("utf-8")
                        data = json.loads(payload_json)
                        if not isinstance(data, dict):
                            raise ValueError("payload must decode to a JSON object")
                        auth_result.update(data)
                        self._send_response("Login successful! You can close this tab and return to the terminal.")
                    except Exception as exc:
                        auth_error = f"Invalid payload: {exc}"
                        self._send_response("Error processing login data.", status=400)

                def _send_fragment_handoff_page(self):
                    self.send_response(200)
                    self.send_header("Content-type", "text/html")
                    self.end_headers()
                    html = """
                    <html>
                    <body style="font-family: sans-serif; text-align: center; padding: 50px;">
                        <h3>HLA-Compass CLI</h3>
                        <p id="status">Finalizing login...</p>
                        <script>
                            (async function () {
                                const statusEl = document.getElementById("status");
                                const hash = new URLSearchParams(window.location.hash.slice(1));
                                const payload = hash.get("payload");
                                const state = hash.get("state");
                                const error = hash.get("error");

                                // Remove sensitive fragment values from browser history.
                                history.replaceState(null, "", window.location.pathname + window.location.search);

                                if (!payload && !error) {
                                    statusEl.textContent = "Missing credentials in callback.";
                                    return;
                                }

                                try {
                                    const resp = await fetch("/cli-callback", {
                                        method: "POST",
                                        headers: { "Content-Type": "application/json" },
                                        body: JSON.stringify({
                                            payload: payload || "",
                                            state: state || "",
                                            error: error || "",
                                        }),
                                    });
                                    const text = await resp.text();
                                    document.open();
                                    document.write(text);
                                    document.close();
                                } catch (_err) {
                                    statusEl.textContent = "Failed to deliver callback to CLI.";
                                }
                            })();
                        </script>
                    </body>
                    </html>
                    """
                    self.wfile.write(html.encode("utf-8"))

                def _send_response(self, message: str, status=200):
                    self.send_response(status)
                    self.send_header("Content-type", "text/html")
                    self.end_headers()
                    safe_message = html_module.escape(message)
                    page = f"""
                    <html>
                    <body style="font-family: sans-serif; text-align: center;
                                 padding: 50px;">
                        <h3>HLA-Compass CLI</h3>
                        <p>{safe_message}</p>
                        <script>window.close()</script>
                    </body>
                    </html>
                    """
                    self.wfile.write(page.encode("utf-8"))

                def log_message(self, format, *args):
                    pass  # Suppress logs

            # Try to bind to a port
            port = 0
            server = None
            try:
                server = socketserver.TCPServer(("127.0.0.1", 0), CallbackHandler)
                port = server.server_address[1]
            except Exception as exc:
                raise AuthError(f"Failed to start local login server: {exc}") from exc

            # 2. Construct Login URL
            console_url = self._console_endpoint()
            callback_url = f"http://127.0.0.1:{port}/cli-callback"

            # Encode callback URL to pass to frontend
            # The frontend at /cli-login should:
            # 1. Authenticate user
            # 2. Construct JSON creds {access_token, refresh_token, ...}
            # 3. Base64 encode it
            # 4. Redirect to {callback_url}?state={state}#{payload=base64_creds}

            query = {"redirect_uri": callback_url, "state": expected_state}
            login_url = f"{console_url}/cli-login?{urlencode(query)}"

            print(f"Opening browser to: {login_url}", flush=True)

            try:
                webbrowser.open(login_url)

                # 3. Wait for callback.
                # Some browsers may send extra requests (e.g. /favicon.ico), so we
                # handle requests in a loop until we receive the expected payload.
                server.timeout = 1
                try:
                    timeout_seconds = int(os.getenv("HLA_COMPASS_CLI_LOGIN_TIMEOUT_SECONDS", "600"))
                except ValueError:
                    timeout_seconds = 600
                deadline = time.time() + max(timeout_seconds, 30)
                while not auth_result and not auth_error and time.time() < deadline:
                    server.handle_request()
            finally:
                server.server_close()

            # 4. Process result
            if auth_error:
                raise AuthError(f"Browser login failed: {auth_error}")

            if not auth_result:
                raise AuthError("Timed out waiting for browser login callback.")

            payload = self._extract_auth_payload(auth_result, "Invalid credential format received from browser.")
            self._save_credentials(payload)
            self._invalidate_cache()
            return payload

    def is_authenticated(self) -> bool:
        """Check if the user is currently authenticated"""
        if self.get_bearer_token() is not None:
            return True
        return self.get_api_key() is not None

    def get_status(self) -> Dict[str, Any]:
        """
        Get current authentication status.

        Returns:
            Dictionary with authentication status information
        """
        status = {
            "authenticated": self.is_authenticated(),
            "environment": self._current_environment(),
            "activeOrgId": Config.get_active_org_id(self._current_environment()),
        }

        # Check for credentials
        if self._credentials_available():
            try:
                creds = self._load_credentials(allow_refresh=False)
                if creds:
                    if creds.get("expires_at"):
                        expires = datetime.fromisoformat(creds["expires_at"])
                        now = datetime.now(timezone.utc) if expires.tzinfo else datetime.now()
                        status["token_expires_at"] = creds["expires_at"]
                        status["token_expired"] = expires <= now
                    status["has_refresh_token"] = bool(creds.get("refresh_token"))
                    status["has_api_key"] = bool(creds.get("api_key"))
            except Exception:
                pass

        # Check environment variables
        status["env_api_key_set"] = bool(self.config.get_api_key())
        status["env_access_token_set"] = bool(self.config.get_access_token())

        return status

    def logout(self):
        """Logout and clear stored credentials"""
        self._clear_stored_credentials()
        self._invalidate_cache()

    def refresh_token(self) -> Optional[str]:
        """
        Refresh access token using refresh token

        Returns:
            New access token or None if refresh fails
        """
        if not self._credentials_available():
            logger.info("Auth refresh requested but no credentials available")
            return None

        try:
            creds = self._load_credentials(allow_refresh=False)
            if not creds:
                return None

            refresh_token = creds.get("refresh_token")
            if not refresh_token:
                logger.info("Auth refresh requested but no refresh token found")
                return None

            endpoint = f"{self._api_endpoint()}/v1/auth/refresh"
            max_attempts = 3

            for attempt in range(max_attempts):
                try:
                    response = self.session.post(
                        endpoint,
                        json={"refresh_token": refresh_token},
                        headers=self._json_headers(),
                        timeout=30,
                    )
                except requests.RequestException as exc:
                    if attempt < max_attempts - 1:
                        time.sleep(min(2**attempt, 30))
                        continue
                    logger.warning("Auth token refresh failed: %s", exc)
                    return None

                if response.status_code == 200:
                    payload = self._extract_auth_payload(response.json(), "Token refresh failed")
                    self._save_credentials(payload, source="refresh")
                    self._invalidate_cache()
                    logger.info("Auth token refreshed successfully")
                    return payload.get("access_token")

                if response.status_code == 429 and attempt < max_attempts - 1:
                    retry_after = min(int(response.headers.get("Retry-After", 2**attempt)), 60)
                    time.sleep(retry_after)
                    continue

                if response.status_code >= 500 and attempt < max_attempts - 1:
                    time.sleep(min(2**attempt, 30))
                    continue

                # Non-retryable or max attempts reached
                break
        except (json.JSONDecodeError, KeyError, ValueError):
            # Token file corrupted or invalid format
            pass
        logger.warning("Auth token refresh failed")
        return None

    def get_bearer_token(self) -> Optional[str]:
        """
        Return a bearer access token if available.

        Lookup order:
          1. HLA_ACCESS_TOKEN environment variable (via Config)
          2. Recently cached credentials
          3. Stored credentials (with automatic refresh for expired tokens)
        """
        token = os.getenv("HLA_ACCESS_TOKEN")
        if token:
            return token

        if self._credentials_cache and self._cache_expiry:
            if time.monotonic() < self._cache_expiry:
                cached = self._credentials_cache.get("access_token")
                if cached:
                    return cached
            else:
                self._invalidate_cache()

        if self._credentials_available():
            creds = self._load_credentials()
            if creds:
                access_token = creds.get("access_token")
                if access_token:
                    return access_token

        return None

    def get_api_key(self) -> Optional[str]:
        """
        Return an API key if available.

        Environment variables take precedence, followed by cached or stored
        credentials. No token refresh is attempted when only an API key exists.
        """
        api_key_env = self.config.get_api_key()
        if api_key_env:
            return api_key_env

        if self._credentials_cache and self._cache_expiry:
            if time.monotonic() < self._cache_expiry:
                api_key_cached = self._credentials_cache.get("api_key")
                if api_key_cached:
                    return api_key_cached
            else:
                self._invalidate_cache()

        if self._credentials_available():
            creds = self._load_credentials(allow_refresh=False)
            if creds:
                api_key_stored = creds.get("api_key")
                if api_key_stored:
                    return api_key_stored

        return None

    def get_access_token(self) -> Optional[str]:
        """
        Backwards-compatible alias for get_bearer_token().

        Historically this method returned either a bearer token or API key,
        which confused downstream consumers. It now only returns bearer tokens;
        callers needing API keys should use get_api_key().
        """
        return self.get_bearer_token()

    def _load_credentials(self, allow_refresh: bool = True) -> Optional[Dict[str, Any]]:
        serialized = self._retrieve_from_keyring()
        if serialized is None:
            serialized = self._read_encrypted_credentials()
        if serialized is None:
            return None

        try:
            creds = json.loads(serialized)

            # Legacy credentials may not carry an environment marker yet.
            if "environment" in creds:
                stored_environment = Config._normalize_environment_name(creds.get("environment"))
                if stored_environment != self._current_environment():
                    return None

            # Check if the token is expired
            expires_at = creds.get("expires_at")
            if expires_at:
                expires = datetime.fromisoformat(expires_at)
                # Compare using a matching clock: naive vs naive, aware vs aware
                now = datetime.now(timezone.utc) if expires.tzinfo else datetime.now()
                if expires <= now:
                    if not allow_refresh:
                        return creds

                    # Token expired, try to refresh exactly once
                    if getattr(self, "_refresh_in_progress", False):
                        return None

                    self._refresh_in_progress = True
                    try:
                        new_token = self.refresh_token()
                    finally:
                        self._refresh_in_progress = False

                    if new_token:
                        refreshed_credentials = self._load_credentials(allow_refresh=False)
                        if refreshed_credentials:
                            return refreshed_credentials
                        return {"access_token": new_token}

                    # Refresh failed; invalidate cache but preserve stored
                    # credentials so that api_key (if present) remains usable.
                    self._invalidate_cache()
                    return None

            self._credentials_cache = creds
            self._cache_expiry = time.monotonic() + 60
            return creds
        except (json.JSONDecodeError, KeyError, ValueError) as e:
            logger.warning("Credential storage corrupted: %s. Clearing stored credentials.", e)
            self._clear_stored_credentials()
            return None

    def get_headers(self) -> Dict[str, str]:
        """
        Get authorization headers for API requests.
        Emits either Authorization: Bearer or X-API-Key header depending on
        credential type.

        Returns:
            Headers dict with authorization token or API key
        """
        headers = {"Content-Type": "application/json"}

        # Optional org context header.
        #
        # Platform behavior: the API gateway only honors X-Organization-Id
        # for privileged roles (org_admin/platform_admin); for most users this
        # header is ignored server-side. We still emit it when configured so
        # privileged users can intentionally override org context.
        org_id = Config.get_active_org_id(self._current_environment())
        if org_id:
            headers["X-Organization-Id"] = str(org_id)

        token = self.get_bearer_token()
        if token:
            headers["Authorization"] = f"Bearer {token}"
            return headers

        api_key = self.get_api_key()
        if api_key:
            headers["X-API-Key"] = api_key

        return headers

    def _save_credentials(self, data: Dict[str, Any], *, source: str = "login"):
        """Save credentials to a file"""
        # Calculate expiration time
        expires_in = data.get("expires_in", 3600)
        expires_at = datetime.now(timezone.utc) + timedelta(seconds=expires_in)

        credentials = {
            "access_token": data.get("access_token"),
            "refresh_token": data.get("refresh_token"),
            "expires_at": expires_at.isoformat(),
            "environment": self._current_environment(),
        }

        # Include API key if present
        if data.get("api_key"):
            credentials["api_key"] = data.get("api_key")

        serialized = json.dumps(credentials)

        if not self._store_in_keyring(serialized):
            self._write_encrypted_credentials(serialized)

        self._invalidate_cache()
        logger.info(
            "Credentials stored",
            extra={
                "auth_event": "credentials_store",
                "environment": self._current_environment(),
                "has_refresh_token": bool(credentials.get("refresh_token")),
                "has_api_key": bool(credentials.get("api_key")),
                "source": source,
            },
        )

    # Secure storage helpers

    def _credentials_available(self) -> bool:
        if keyring and self._retrieve_from_keyring() is not None:
            return True
        return self.credentials_path.exists()

    def _store_in_keyring(self, serialized: str) -> bool:
        if not keyring:
            return False

        account = self._keyring_account()
        try:
            keyring.set_password(self._keyring_service, account, serialized)
            return True
        except KeyringError as err:  # pragma: no cover - depends on system keyring
            logger.warning("Keyring storage failed (%s). Falling back to encrypted file.", err)
            return False

    def _retrieve_from_keyring(self) -> Optional[str]:
        if not keyring:
            return None
        account = self._keyring_account()
        try:
            return keyring.get_password(self._keyring_service, account)
        except KeyringError as err:  # pragma: no cover - depends on system keyring
            logger.warning("Failed to read credentials from keyring: %s", err)
            return None

    def _keyring_account(self) -> str:
        env = self._current_environment()
        return f"{env}:tokens"

    def _write_encrypted_credentials(self, serialized: str) -> None:
        key = self._get_encryption_key()
        token = Fernet(key).encrypt(serialized.encode("utf-8"))

        self.credentials_path.parent.mkdir(parents=True, exist_ok=True)
        with open(self.credentials_path, "wb") as f:
            f.write(token)

        try:
            self.credentials_path.chmod(0o600)
        except (OSError, NotImplementedError):  # pragma: no cover - platform specific
            pass

    def _read_encrypted_credentials(self) -> Optional[str]:
        if not self.credentials_path.exists():
            return None

        key = self._get_encryption_key()
        raw_bytes = self.credentials_path.read_bytes()
        try:
            data = Fernet(key).decrypt(raw_bytes)
            return data.decode("utf-8")
        except Exception as exc:
            # Fallback for legacy plaintext credentials written before encryption
            # support.
            try:
                plaintext = raw_bytes.decode("utf-8")
                json.loads(plaintext)
            except Exception:
                logger.warning("Failed to decrypt stored credentials: %s", exc)
                self._clear_stored_credentials()
                return None

            logger.info("Detected legacy plaintext credential file; migrating to encrypted storage")
            # Re-encrypt credentials to move forward securely
            self._write_encrypted_credentials(plaintext)
            return plaintext

    def _get_encryption_key(self) -> bytes:
        env_key = os.getenv("HLA_COMPASS_CREDENTIAL_KEY")
        if env_key:
            try:
                key_bytes = env_key.encode("utf-8")
                decoded = base64.urlsafe_b64decode(key_bytes)
                if len(decoded) != 32:
                    raise ValueError
                return key_bytes
            except Exception as exc:
                raise AuthError(
                    "Invalid HLA_COMPASS_CREDENTIAL_KEY; must be urlsafe base64-encoded 32-byte key"
                ) from exc

        key_path = Path(self.credentials_path.parent) / "credentials.key"
        if key_path.exists():
            return key_path.read_bytes()

        key = Fernet.generate_key()
        key_path.parent.mkdir(parents=True, exist_ok=True)
        key_path.write_bytes(key)
        try:
            key_path.chmod(0o600)
        except (OSError, NotImplementedError):  # pragma: no cover
            pass
        return key

    def _clear_stored_credentials(self) -> None:
        if keyring:
            try:
                keyring.delete_password(self._keyring_service, self._keyring_account())
            except KeyringError:  # pragma: no cover - depends on keyring backend
                pass
        if self.credentials_path.exists():
            try:
                self.credentials_path.unlink()
            except OSError:
                pass
        key_path = Path(self.credentials_path.parent) / "credentials.key"
        if key_path.exists():
            try:
                key_path.unlink()
            except OSError:
                pass
        logger.info(
            "Credentials cleared",
            extra={
                "auth_event": "credentials_cleared",
                "environment": self._current_environment(),
            },
        )
