"""
DynamoDB client for job state management.

Single Responsibility: CRUD operations on czbg-jobs table.
"""

import os
from datetime import datetime, timedelta
from decimal import Decimal
from typing import Any, Optional

import boto3
from botocore.exceptions import ClientError


def convert_floats_to_decimal(obj: Any) -> Any:
    """
    Recursively convert float values to Decimal for DynamoDB.

    DynamoDB doesn't support float types - only Decimal for numbers.

    Args:
        obj: Python object (dict, list, or primitive)

    Returns:
        Object with floats converted to Decimal
    """
    if isinstance(obj, float):
        return Decimal(str(obj))
    elif isinstance(obj, dict):
        return {k: convert_floats_to_decimal(v) for k, v in obj.items()}
    elif isinstance(obj, list):
        return [convert_floats_to_decimal(item) for item in obj]
    else:
        return obj


class DynamoDBClient:
    """
    Thin wrapper around boto3 DynamoDB client.

    Handles all job state persistence operations.
    Max 5 methods per CLAUDE.md guidelines.
    """

    def __init__(self, table_name: str, region_name: str = "us-east-1"):
        """
        Initialize DynamoDB client.

        Args:
            table_name: DynamoDB table name (e.g., 'czbg-jobs-dev')
            region_name: AWS region
        """
        self.table_name = table_name

        # Use session to respect AWS_PROFILE environment variable
        session = boto3.Session(
            profile_name=os.getenv("AWS_PROFILE"),
            region_name=region_name
        )
        self.dynamodb = session.resource("dynamodb")
        self.table = self.dynamodb.Table(table_name)

    def create_job(self, job_metadata: dict) -> None:
        """
        Create new job record in DynamoDB.

        Args:
            job_metadata: Job attributes including job_id, user_id, etc.

        Raises:
            ClientError: If DynamoDB operation fails
        """
        # Add TTL for automatic cleanup (30 days from now)
        ttl_timestamp = int((datetime.utcnow() + timedelta(days=30)).timestamp())
        job_metadata["ttl"] = ttl_timestamp

        try:
            self.table.put_item(Item=job_metadata)
        except ClientError as e:
            raise RuntimeError(
                f"Failed to create job record in DynamoDB: {e}"
            ) from e

    def get_job(self, job_id: str) -> Optional[dict]:
        """
        Retrieve job record by job_id.

        Args:
            job_id: Unique job identifier

        Returns:
            Job record dict or None if not found

        Raises:
            ClientError: If DynamoDB operation fails
        """
        try:
            response = self.table.get_item(Key={"job_id": job_id})
            return response.get("Item")
        except ClientError as e:
            raise RuntimeError(
                f"Failed to retrieve job {job_id} from DynamoDB: {e}"
            ) from e

    def update_job(self, job_id: str, updates: dict) -> None:
        """
        Update job attributes.

        Args:
            job_id: Job to update
            updates: Dict of attributes to update (e.g., {'status': 'running'})

        Raises:
            ClientError: If DynamoDB operation fails
        """
        # Convert floats to Decimal for DynamoDB compatibility
        updates = convert_floats_to_decimal(updates)

        # Build update expression from updates dict
        # Use ExpressionAttributeNames to handle reserved keywords (like 'status')
        update_expr_parts = []
        expr_attr_values = {}
        expr_attr_names = {}

        for key, value in updates.items():
            # Use placeholder for attribute name to avoid reserved keyword issues
            attr_name_placeholder = f"#{key}"
            attr_value_placeholder = f":{key}"

            update_expr_parts.append(f"{attr_name_placeholder} = {attr_value_placeholder}")
            expr_attr_names[attr_name_placeholder] = key
            expr_attr_values[attr_value_placeholder] = value

        update_expression = "SET " + ", ".join(update_expr_parts)

        try:
            self.table.update_item(
                Key={"job_id": job_id},
                UpdateExpression=update_expression,
                ExpressionAttributeNames=expr_attr_names,
                ExpressionAttributeValues=expr_attr_values,
            )
        except ClientError as e:
            raise RuntimeError(
                f"Failed to update job {job_id} in DynamoDB: {e}"
            ) from e

    def query_jobs_by_user(
        self, user_id: str, limit: int = 10
    ) -> list[dict]:
        """
        Query recent jobs for a user.

        Args:
            user_id: User identifier
            limit: Maximum number of jobs to return

        Returns:
            List of job records, most recent first

        Raises:
            ClientError: If DynamoDB operation fails
        """
        try:
            response = self.table.query(
                IndexName="user_id-created_at-index",
                KeyConditionExpression="user_id = :user_id",
                ExpressionAttributeValues={":user_id": user_id},
                Limit=limit,
                ScanIndexForward=False,  # Most recent first
            )
            return response.get("Items", [])
        except ClientError as e:
            raise RuntimeError(
                f"Failed to query jobs for user {user_id}: {e}"
            ) from e
