"""Amazon S3 uploader implementation."""

from pathlib import Path

import boto3

from botocore.client import BaseClient
from data_exchange_agent.constants.task_keys import TargetTypeValues
from data_exchange_agent.interfaces.uploader import UploaderInterface
from data_exchange_agent.utils.decorators import log_error


class AmazonS3Uploader(UploaderInterface):
    """Uploader class for Amazon S3."""

    BUCKET_NAME_KEY = "bucket_name"
    PROFILE_NAME_KEY = "profile_name"
    REGION_NAME_KEY = "region_name"

    @log_error
    def configure(self) -> None:
        """
        Configure the Amazon S3 uploader.

        Validates and extracts required S3 configuration parameters from the provided
        cloud storage TOML dictionary. Sets up the bucket name and profile name needed
        for S3 connections.

        Args:
            None

        Raises:
            ValueError: If cloud storage configuration not found.
            KeyError: If required S3 configuration parameters are missing from the TOML.
                      This includes checking for:
                      - S3 section in configuration
                      - Bucket name in S3 section
                      - Profile name in S3 section

        """
        # Validate configuration
        if not self.cloud_storage_toml:
            raise ValueError(
                "Cloud storage configuration not found."
                " Check if configuration TOML file exists and if Amazon S3 profile name is set."
            )
        if TargetTypeValues.S3 not in self.cloud_storage_toml:
            raise KeyError(
                f"'{TargetTypeValues.S3}' key not found in cloud storage section in the TOML file."
            )
        if self.BUCKET_NAME_KEY not in self.cloud_storage_toml[TargetTypeValues.S3]:
            raise KeyError(
                f"'{self.BUCKET_NAME_KEY}' not found in S3 section in the TOML file."
            )
        if self.PROFILE_NAME_KEY not in self.cloud_storage_toml[TargetTypeValues.S3]:
            raise KeyError(
                f"'{self.PROFILE_NAME_KEY}' not found in S3 section in the TOML file."
            )

        # Initialize attributes
        self.s3_client: BaseClient | None = None
        self.bucket_name: str = self.cloud_storage_toml[TargetTypeValues.S3][
            self.BUCKET_NAME_KEY
        ]
        self.profile_name: str = self.cloud_storage_toml[TargetTypeValues.S3][
            self.PROFILE_NAME_KEY
        ]
        self.region_name: str | None = self.cloud_storage_toml[TargetTypeValues.S3].get(
            self.REGION_NAME_KEY, None
        )

    @log_error
    def connect(self) -> None:
        """Connect to Amazon S3."""
        # Create session with profile, then create client from session
        if self.s3_client:
            return
        if self.region_name:
            session = boto3.Session(
                profile_name=self.profile_name, region_name=self.region_name
            )
        else:
            session = boto3.Session(profile_name=self.profile_name)
        self.s3_client = session.client("s3")

    @log_error
    def disconnect(self) -> None:
        """Disconnect from Amazon S3."""
        if not self.s3_client:
            return
        self.s3_client.close()
        self.s3_client = None

    @log_error
    def upload_file(self, source_path: str, destination_path: str) -> None:
        """Upload a file to Amazon S3."""
        # Validate file exists
        if not Path(source_path).exists():
            raise FileNotFoundError(f"File not found: {source_path}")

        # Connect to S3 if not already connected
        if not self.s3_client:
            self.connect()

        # Get filename from source path
        file_name = Path(source_path).name

        # Create full destination file path, avoiding leading slash when destination is empty
        clean_destination = destination_path.strip("/")
        if clean_destination:
            full_destination_file_path = f"{clean_destination}/{file_name}"
        else:
            full_destination_file_path = file_name

        # Upload file to S3
        if self.s3_client is None:
            raise RuntimeError("S3 client not connected. Call connect() first.")
        self.s3_client.upload_file(
            Filename=source_path,
            Bucket=self.bucket_name,
            Key=full_destination_file_path,
        )

    def upload_files(self, *source_files: str, destination_path: str) -> None:
        """
        Upload a list of files to Amazon S3.

        Args:
            *source_files: Variable length argument list of source file paths to upload.
            destination_path: The destination path to upload the files to.

        Returns:
            None

        """
        for source_file in source_files:
            self.upload_file(source_file, destination_path)
