import os
import logging
from typing import Any, Dict, Optional

import boto3
from botocore.exceptions import ClientError
from botocore.client import Config

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

class S3:
    def __init__(self, region_name: str = None):
        region = region_name or os.getenv("AWS_DEFAULT_REGION", "us-east-1")
        self.client = boto3.client(
            "s3",
            region_name=region,
            config=Config(signature_version="s3v4")
        )

    # -----------------------------------------
    # Validation Helper
    # -----------------------------------------
    @staticmethod
    def _validate(bucket: Optional[str], file: Optional[str]) -> None:
        if not bucket:
            raise ValueError("bucket name is invalid")
        if not file:
            raise ValueError("file name is invalid")

    # -----------------------------------------
    # Basic File Operations
    # -----------------------------------------
    def get_s3_file(self, bucket: str, file: str) -> Optional[str]:
        logger.info("AWSUtility : get_s3_file Start")
        try:
            self._validate(bucket, file)

            resp = self.client.get_object(Bucket=bucket, Key=file)
            body = resp.get("Body")
            if body is None:
                return None

            content = body.read().decode("utf-8")
            logger.info("s3 response - %s", content)
            logger.info("AWSUtility : get_s3_file End")
            return content

        except Exception:
            logger.exception("AWSUtility : get_s3_file Error")
            raise

    def get_s3_file_stream(self, bucket: str, file: str):
        logger.info("AWSUtility : get_s3_file_stream Start")
        try:
            self._validate(bucket, file)
            resp = self.client.get_object(Bucket=bucket, Key=file)
            logger.info("AWSUtility : get_s3_file_stream End")
            return resp.get("Body")
        except Exception:
            logger.exception("AWSUtility : get_s3_file_stream Error")
            raise

    def get_s3_raw_file(self, bucket: str, file: str) -> Dict[str, Any]:
        logger.info("AWSUtility : get_s3_raw_file Start")
        try:
            self._validate(bucket, file)
            resp = self.client.get_object(Bucket=bucket, Key=file)
            logger.info("AWSUtility : get_s3_raw_file End")
            return resp
        except Exception:
            logger.exception("AWSUtility : get_s3_raw_file Error")
            raise

    def delete_s3_file(self, bucket: str, file: str, content: Any = None) -> Any:
        logger.info("AWSUtility : delete_s3_file Start")
        try:
            self._validate(bucket, file)
            resp = self.client.delete_object(Bucket=bucket, Key=file)
            logger.info("%s deleted successfully from s3 %s", file, resp)
            logger.info("AWSUtility : delete_s3_file End")
            return content
        except Exception:
            logger.exception("AWSUtility : delete_s3_file Error")
            raise

    def put_s3_file(
        self,
        bucket: str,
        file: str,
        content: Any,
        content_type: Optional[str] = None,
        acl: bool = True
    ) -> Any:
        if content is None:
            raise ValueError("content is invalid")

        logger.info("AWSUtility : put_s3_file Start")
        try:
            self._validate(bucket, file)

            params = {"Bucket": bucket, "Key": file, "Body": content}
            if content_type:
                params["ContentType"] = content_type
            if acl:
                params["ACL"] = "public-read"

            resp = self.client.put_object(**params)
            logger.info("%s uploaded successfully to s3 %s", file, resp)
            logger.info("AWSUtility : put_s3_file End")
            return content
        except Exception:
            logger.exception("AWSUtility : put_s3_file Error")
            raise

    # -----------------------------------------
    # List Objects
    # -----------------------------------------
    def get_s3_list_objects(self, params: Dict[str, Any]) -> Dict[str, Any]:
        try:
            return self.client.list_objects_v2(**params)
        except Exception:
            logger.exception("get_s3_list_objects Error")
            raise

    # -----------------------------------------
    # Presigned URL
    # -----------------------------------------
    def get_s3_presigned_url(self, bucket: str, key: str, expires_in: int = 3600) -> str:
        try:
            return self.client.generate_presigned_url(
                "get_object",
                Params={"Bucket": bucket, "Key": key},
                ExpiresIn=expires_in,
            )
        except Exception:
            logger.exception("get_s3_presigned_url Error")
            raise

    # -----------------------------------------
    # Multipart Upload
    # -----------------------------------------
    def create_multipart_upload(self, params: Dict[str, Any]) -> Dict[str, Any]:
        try:
            return self.client.create_multipart_upload(**params)
        except Exception:
            logger.exception("create_multipart_upload Error")
            raise

    def upload_part(self, params: Dict[str, Any]) -> Dict[str, Any]:
        try:
            return self.client.upload_part(**params)
        except Exception:
            logger.exception("upload_part Error")
            raise

    def complete_multipart_upload(self, params: Dict[str, Any]) -> Dict[str, Any]:
        try:
            return self.client.complete_multipart_upload(**params)
        except Exception:
            logger.exception("complete_multipart_upload Error")
            raise

    def abort_multipart_upload(self, params: Dict[str, Any]) -> Dict[str, Any]:
        try:
            return self.client.abort_multipart_upload(**params)
        except Exception:
            logger.exception("abort_multipart_upload Error")
            raise
