"""
Utility functions for getbiji_box.
"""

import base64
import hashlib
import os
import time
from typing import Any, Dict, Optional


def calculate_md5(file_path: str) -> str:
    """
    Calculate MD5 hash of a file.

    Args:
        file_path: Path to the file

    Returns:
        MD5 hash as a base64-encoded string (required by GetBiji API)

    Raises:
        IOError: If file cannot be read
    """
    md5_hash = hashlib.md5()
    with open(file_path, "rb") as f:
        for chunk in iter(lambda: f.read(8192), b""):
            md5_hash.update(chunk)
    # Convert raw digest bytes to base64 (Content-MD5 standard)
    return base64.b64encode(md5_hash.digest()).decode()


def calculate_file_size(file_path: str) -> int:
    """
    Get file size in bytes.

    Args:
        file_path: Path to the file

    Returns:
        File size in bytes
    """
    return os.path.getsize(file_path)


def get_audio_duration_ms(file_path: str) -> int:
    """
    Get audio duration in milliseconds.

    This is a placeholder - in a real implementation you would use
    a library like pydub or audioread to get the actual duration.

    Args:
        file_path: Path to the audio file

    Returns:
        Duration in milliseconds (estimated based on file size)

    Note:
        For production use, install audioread or similar:
        pip install audioread

        And implement:
        import audioread
        with audioread.audio_open(file_path) as f:
            return int(f.duration * 1000)
    """
    # Rough estimation: 1KB ≈ 0.1 second for MP3 at 128kbps
    # This is NOT accurate - use audioread for real applications
    size = calculate_file_size(file_path)
    estimated_duration = size * 100 / 1024  # Very rough estimate
    return int(estimated_duration * 1000)  # Convert to milliseconds


def validate_audio_file(file_path: str) -> bool:
    """
    Validate that a file is a supported audio format.

    Args:
        file_path: Path to the file

    Returns:
        True if file appears to be valid audio

    Note:
        This is a basic check - use proper validation in production.
    """
    if not os.path.exists(file_path):
        return False

    ext = os.path.splitext(file_path)[1].lower()
    supported = [".mp3", ".m4a", ".wav", ".aac", ".ogg", ".flac"]
    return ext in supported


def get_file_extension(file_path: str) -> str:
    """
    Get file extension without the dot.

    Args:
        file_path: Path to the file

    Returns:
        Lowercase file extension (e.g., "mp3")
    """
    ext = os.path.splitext(file_path)[1].lower()
    return ext.lstrip(".")


def extract_csrf_token(headers: Dict[str, str]) -> Optional[str]:
    """
    Extract CSRF token from response headers if present.

    Args:
        headers: Response headers dictionary

    Returns:
        CSRF token if found, None otherwise
    """
    # Check common CSRF header locations
    for key in ["x-csrf-token", "x-xsrf-token", "csrf-token"]:
        value = headers.get(key)
        if value:
            return value
    return None


def generate_request_id() -> str:
    """
    Generate a unique request ID (timestamp-based).

    Returns:
        String representation of current timestamp in milliseconds
    """
    return str(int(time.time() * 1000))


def format_json_content(content: str) -> str:
    """
    Format plain text content into the rich text JSON structure required by API.

    Args:
        content: Plain text content

    Returns:
        JSON string formatted for the API
    """
    import json

    # Build the JSON content structure
    json_content = {
        "type": "doc",
        "content": [
            {
                "type": "paragraph",
                "attrs": {
                    "lineHeight": "100%",
                    "textAlign": None,
                    "class": "",
                    "indent": 0,
                },
                "content": [{"type": "text", "text": content}],
            }
        ],
    }

    return json.dumps(json_content, ensure_ascii=False)


def sanitize_filename(filename: str) -> str:
    """
    Sanitize a filename by removing/replacing invalid characters.

    Args:
        filename: Original filename

    Returns:
        Sanitized filename
    """
    # Remove or replace invalid characters
    invalid_chars = '<>:"/\\|?*'
    for char in invalid_chars:
        filename = filename.replace(char, "_")
    return filename.strip()


def extract_error_message(response_data: Dict[str, Any]) -> str:
    """
    Extract error message from API response.

    Args:
        response_data: Parsed JSON response from API

    Returns:
        Error message string
    """
    # Check various possible error message locations
    if "h" in response_data:
        h = response_data["h"]
        if isinstance(h, dict):
            return h.get("e", "Unknown error")

    # Check for error in 'c' section
    if "c" in response_data:
        c = response_data["c"]
        if isinstance(c, dict) and "error" in c:
            return c["error"]

    return "Unknown error"


def is_token_expired(expire_at: int, buffer_seconds: int = 300) -> bool:
    """
    Check if a token is expired or will expire soon.

    Args:
        expire_at: Token expiration timestamp (seconds)
        buffer_seconds: Buffer time in seconds (default: 5 minutes)

    Returns:
        True if token is expired or will expire within buffer time
    """
    current_time = int(time.time())
    return current_time >= (expire_at - buffer_seconds)


def parse_note_id_from_url(url: str) -> Optional[str]:
    """
    Try to extract note ID from a GetBiji URL.

    Args:
        url: GetBiji note URL

    Returns:
        Note ID if found, None otherwise
    """
    import re

    # Pattern for GetBiji note URLs
    patterns = [
        r"/notes/([a-zA-Z0-9_-]+)",
        r"/note/([a-zA-Z0-9_-]+)",
        r"noteId=([a-zA-Z0-9_-]+)",
    ]

    for pattern in patterns:
        match = re.search(pattern, url)
        if match:
            return match.group(1)

    return None


def build_json_content(content: str, content_type: str = "paragraph", **attrs) -> str:
    """
    Build JSON content for note creation.

    Args:
        content: Text content
        content_type: Type of content block (paragraph, heading, etc.)
        **attrs: Additional attributes for the content block

    Returns:
        JSON string for the json_content field
    """
    import json

    default_attrs = {
        "lineHeight": "100%",
        "textAlign": None,
        "class": "",
        "indent": 0,
    }
    default_attrs.update(attrs)

    json_content = {
        "type": "doc",
        "content": [
            {
                "type": content_type,
                "attrs": default_attrs,
                "content": [{"type": "text", "text": content}],
            }
        ],
    }

    return json.dumps(json_content, ensure_ascii=False)


def safe_get(data: Dict[str, Any], *keys, default=None) -> Any:
    """
    Safely get nested values from a dictionary.

    Args:
        data: Dictionary to traverse
        *keys: Keys to traverse in order
        default: Default value if key not found

    Returns:
        Value at the nested key path, or default if not found
    """
    current = data
    for key in keys:
        if isinstance(current, dict) and key in current:
            current = current[key]
        else:
            return default
    return current


def chunks(lst: list, n: int) -> list:
    """
    Split a list into chunks of size n.

    Args:
        lst: List to split
        n: Chunk size

    Returns:
        List of chunks
    """
    return [lst[i : i + n] for i in range(0, len(lst), n)]


def retry_on_failure(
    func,
    max_retries: int = 3,
    retry_exceptions: tuple = (Exception,),
):
    """
    Simple retry decorator for API calls.

    Args:
        func: Function to retry
        max_retries: Maximum number of retry attempts
        retry_exceptions: Tuple of exceptions to catch and retry on

    Returns:
        Function result or raises last exception
    """

    def wrapper(*args, **kwargs):
        last_exception = None
        for attempt in range(max_retries):
            try:
                return func(*args, **kwargs)
            except retry_exceptions as e:
                last_exception = e
                if attempt < max_retries - 1:
                    time.sleep(2**attempt)  # Exponential backoff
                continue
        raise last_exception

    return wrapper
