"""Meta information data model for transfer operations.

This module provides data models for storing metadata about reports, models,
and other creation types.
"""

from pathlib import Path

from pydantic import BaseModel, ConfigDict, Field

from hezor_common.security.jwt import encode_jwt_with_file, encode_jwt_with_pem
from hezor_common.transfer.base_sdk.constants import REQ_HEADER_META_INFO_KEY


class MetaInfo(BaseModel):
    """Meta information for reports and models.

    This model stores essential metadata about a creation, including subject
    information, caller details, data coverage period, and creation type.

    Attributes
    ----------
    subject : str
        The name of the subject/entity
    subject_code : str
        The unique code identifying the subject
    caller_id : str
        The ID of the caller/requester
    data_coverage : str
        The data coverage period in format "yyyyMM-yyyyMM" or "yyyyMMdd-yyyyMMdd"
    creation_slug : str
        The unique identifier for the creation type (e.g., "single_store_profit_model")
    creation_name : str
        The human-readable name of the creation type (e.g., "单店盈利模型")

    Examples
    --------
    >>> meta = MetaInfo(
    ...     subject="鮨大山",
    ...     subject_code="wdyl_001",
    ...     caller_id="user_123",
    ...     data_coverage="202401-202412",
    ...     creation_slug="single_store_profit_model",
    ...     creation_name="单店盈利模型"
    ... )
    >>> meta.subject
    '鮨大山'
    >>> meta.data_coverage
    '202401-202412'
    """

    model_config = ConfigDict(frozen=False, extra="ignore")

    subject: str = Field(..., description="主体名称")
    subject_code: str = Field(
        ...,
        description="主体编码，支持单主体（如 'yidashan'）或多级主体（主体/子主体格式，如 'yidashan/market_dept'）",
    )
    caller_id: str = Field(..., description="调用者ID")
    data_coverage: str = Field(
        default="",
        description='报告数据周期/数据覆盖范围，格式："yyyyMM-yyyyMM" 或 "yyyyMMdd-yyyyMMdd"',
    )
    creation_slug: str = Field(
        default="", description="报告/模型类型识别码，如 `single_store_profit_model`"
    )
    creation_name: str = Field(default="", description='报告类型名称，如"单店盈利模型"')

    def to_request_header(
        self,
        private_key_path: str | Path | None = None,
        private_key_pem: str | bytes | None = None,
        password: bytes | None = None,
        expires_in: int = 3600,
    ) -> dict[str, str]:
        """Generate request header with JWT-encoded metadata.

        This method encodes the MetaInfo as a JWT token using the provided
        private key and returns it in a request header format.

        Parameters
        ----------
        private_key_path : str | Path | None, optional
            Path to PEM-encoded private key file
        private_key_pem : str | bytes | None, optional
            PEM-encoded private key content (if path is not provided)
        password : bytes | None, optional
            Password for decrypting the private key (default: None)
        expires_in : int, optional
            Token expiration time in seconds (default: 3600 seconds)

        Returns
        -------
        dict[str, str]
            Dictionary containing the "X-META-INFO" header with JWT token

        Raises
        ------
        ValueError
            If neither private_key_path nor private_key_pem is provided,
            or if the password is incorrect and the key cannot be decrypted
        FileNotFoundError
            If private_key_path does not exist
        cryptography.exceptions.UnsupportedAlgorithm
            If the key file is not a valid Ed25519 private key
        TypeError
            If the password type is incorrect (expected bytes or None)

        Examples
        --------
        >>> from pathlib import Path
        >>> meta = MetaInfo(
        ...     subject="鮨大山",
        ...     subject_code="wdyl_001",
        ...     caller_id="user_123",
        ...     data_coverage="202401-202412",
        ...     creation_slug="single_store_profit_model",
        ...     creation_name="单店盈利模型"
        ... )
        >>> # Using file path
        >>> header = meta.to_request_header(private_key_path="private_key.pem", password=b"pass")
        >>> "X-META-INFO" in header
        True
        >>> # Using PEM content
        >>> pem_content = "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
        >>> header = meta.to_request_header(private_key_pem=pem_content)
        >>> isinstance(header["X-META-INFO"], str)
        True
        """
        # Convert MetaInfo to dictionary payload
        payload = self.model_dump()

        try:
            token = self._encode_token(
                payload,
                private_key_path=private_key_path,
                private_key_pem=private_key_pem,
                password=password,
                expires_in=expires_in,
            )
        except ValueError as e:
            if "could not decrypt" not in str(e).lower():
                raise

            # 密码错误时，若未使用匿名密码则用匿名密码重试
            from hezor_common.transfer.base_sdk.constants import (
                ANONYMOUS_HEADER_PRIVATE_KEY_PASSWORD,
            )

            if password is not None and password == ANONYMOUS_HEADER_PRIVATE_KEY_PASSWORD:
                raise

            token = self._encode_token(
                payload,
                private_key_path=private_key_path,
                private_key_pem=private_key_pem,
                password=ANONYMOUS_HEADER_PRIVATE_KEY_PASSWORD,
                expires_in=expires_in,
            )

        # Return as request header
        return {REQ_HEADER_META_INFO_KEY: token}

    @staticmethod
    def _encode_token(
        payload: dict,
        *,
        private_key_path: str | Path | None,
        private_key_pem: str | bytes | None,
        password: bytes | None,
        expires_in: int,
    ) -> str:
        """Encode payload as JWT token.

        Parameters
        ----------
        payload : dict
            JWT payload.
        private_key_path : str | Path | None
            Path to PEM private key file.
        private_key_pem : str | bytes | None
            PEM private key content.
        password : bytes | None
            Password for the private key.
        expires_in : int
            Token expiration in seconds.

        Returns
        -------
        str
            Encoded JWT token.
        """
        if private_key_path is not None:
            return encode_jwt_with_file(
                private_key_path, payload, password=password, expires_in=expires_in
            )
        if private_key_pem is not None:
            pem_bytes = (
                private_key_pem.encode("utf-8")
                if isinstance(private_key_pem, str)
                else private_key_pem
            )
            return encode_jwt_with_pem(pem_bytes, payload, password=password, expires_in=expires_in)
        msg = "Either private_key_path or private_key_pem must be provided"
        raise ValueError(msg)
