"""
S3 client for config file operations.

Single Responsibility: Upload and download job configs from S3.
"""

import os
from pathlib import Path

import boto3
from botocore.exceptions import ClientError


class S3Client:
    """
    Thin wrapper around boto3 S3 client.

    Handles config file storage for API jobs.
    Max 4 methods per CLAUDE.md guidelines.
    """

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

        Args:
            bucket_name: S3 bucket name
            region_name: AWS region
        """
        self.bucket_name = bucket_name

        # Use session to respect AWS_PROFILE environment variable
        session = boto3.Session(
            profile_name=os.getenv("AWS_PROFILE"),
            region_name=region_name
        )
        self.s3 = session.client("s3")

    def upload_file(self, local_path: Path, s3_key: str) -> str:
        """
        Upload file to S3.

        Args:
            local_path: Local file path
            s3_key: S3 object key

        Returns:
            S3 URI (s3://bucket/key)

        Raises:
            ClientError: If S3 operation fails
        """
        try:
            self.s3.upload_file(str(local_path), self.bucket_name, s3_key)
            return f"s3://{self.bucket_name}/{s3_key}"
        except ClientError as e:
            raise RuntimeError(f"Failed to upload file to S3: {e}") from e

    def download_file(self, s3_key: str, local_path: Path) -> None:
        """
        Download file from S3.

        Args:
            s3_key: S3 object key
            local_path: Local destination path

        Raises:
            ClientError: If S3 operation fails
        """
        try:
            # Ensure parent directory exists
            local_path.parent.mkdir(parents=True, exist_ok=True)
            self.s3.download_file(self.bucket_name, s3_key, str(local_path))
        except ClientError as e:
            raise RuntimeError(f"Failed to download file from S3: {e}") from e

    def list_objects(self, prefix: str) -> list[str]:
        """
        List objects under S3 prefix.

        Args:
            prefix: S3 key prefix

        Returns:
            List of S3 keys

        Raises:
            ClientError: If S3 operation fails
        """
        try:
            response = self.s3.list_objects_v2(
                Bucket=self.bucket_name, Prefix=prefix
            )
            return [obj["Key"] for obj in response.get("Contents", [])]
        except ClientError as e:
            raise RuntimeError(f"Failed to list objects in S3: {e}") from e

    def get_object_content(self, s3_key: str) -> str:
        """
        Get object content as string (for small files).

        Args:
            s3_key: S3 object key

        Returns:
            File content as string

        Raises:
            ClientError: If S3 operation fails
        """
        try:
            response = self.s3.get_object(Bucket=self.bucket_name, Key=s3_key)
            return response["Body"].read().decode("utf-8")
        except ClientError as e:
            raise RuntimeError(f"Failed to get object content from S3: {e}") from e

    def put_object(self, s3_key: str, content: str) -> str:
        """
        Upload string content directly to S3.

        Args:
            s3_key: S3 object key
            content: String content to upload

        Returns:
            S3 URI (s3://bucket/key)

        Raises:
            RuntimeError: If S3 operation fails
        """
        try:
            self.s3.put_object(
                Bucket=self.bucket_name,
                Key=s3_key,
                Body=content.encode("utf-8"),
            )
            return f"s3://{self.bucket_name}/{s3_key}"
        except ClientError as e:
            raise RuntimeError(f"Failed to put object to S3: {e}") from e
