"""
input:
- starting_storage_class
- target_storage_class
- total_storage_gb
- data_retrieved_gb
- initial_storage_class_days
- days_before_expiration
"""

# Import our CLI args
from cli import ARGS
from s3_data import data


def calculate_months(days: int) -> float:
    """
    Calculates number of months from input of 'days', using the average number of days in a normal year.

    This represents the expected lifetime of an object in S3 before it is expired
    By default it is set to one year, to provide forcasting abilities for folks who aren't deleting things

    Args:
        days: Number of days objects will exist in total

    Returns:
        int, number of months calculated from days
    """
    days_per_month = 30.416666666666668

    return days / days_per_month


def calculate_data_retrieval_cost(
    months: int, target_storage_class: str, data_retrieved_gb: float
) -> float:
    """
    Calculates the potential cost of data retrieval over a given time period in months, default is 12 months

    Args:
        months: number of months objects will exist, target_storage_class_months is used for this
        target_storage_class: the S3 storage class that is being targeted
        data_retrieved_gb: number of gb retrieved per month

    Returns:
        int, the total cost of data retrieval over the course of the lifetime of the object in the secondary storage class
    """
    target_storage_class_data = data[target_storage_class]

    retrieval_cost = (
        data_retrieved_gb * target_storage_class_data["data_retrieval_cost_per_gb"]
    ) * months

    return retrieval_cost


def calculate_transition_cost(number_objs: int, target_storage_class: str) -> float:
    """
    Calculates the cost of transition data from one class to another

    Args:
        number_objs: the number of objects that are added on a monthly basis
        target_storage_class: the storage class the objects will reside in after they are transitioned

    Returns:
        int, the cost of the transition
    """
    target_storage_class_data = data[target_storage_class]

    transition_cost = (
        number_objs / target_storage_class_data["items_per_transition_chunk"]
    ) * target_storage_class_data["transition_cost"]

    return transition_cost


def calculate_storage_cost(
    months: float, storage_class: str, total_storage_gb: int, number_objs: int
) -> float:
    """
    Calculates storage cost for a given storage class

    Args:
        months: number of months
        storage_class: The storage class
        total_storage_gb: total number of gb that will be stored
    
    Returns:
        float: cost of storage in the specified class
    """
    # Number of days in an average month, used in glacier 90 day minimum calculation
    days_per_month = 30.416666666666668
    storage_class_data = data[storage_class]

    # In Glacier, each object has 40kb of metadata associated with it, defaults to 0
    metadata_storage_cost = 0

    if storage_class == "glacier":
        # 8kb per object in Standard
        standard_metadata = (number_objs * 0.000008) * data["standard"]["price_per_gb"]

        # 32kb per object in Glacier
        glacier_metadata = (number_objs * 0.000032) * storage_class_data["price_per_gb"]

        # Calculate the metadata_storage_cost by adding the above together
        metadata_storage_cost = standard_metadata + glacier_metadata

        # In glacier you are charged for 90 days of storage at a minimum, post transition
        if (months / days_per_month) < 90.00:
            months: 3.0

    # Total cost in in the given storage class for given months (can be partial months)
    storage_cost = (
        (total_storage_gb * storage_class_data["price_per_gb"]) + metadata_storage_cost
    ) * months

    return storage_cost


def handler(
    starting_storage_class: str,
    target_storage_class: str,
    total_storage_gb: float,
    number_objs: int,
    data_retrieved_gb: float,
    initial_storage_class_days: int,
    total_days: int,
) -> bool:
    """
    Main entrypoint for the calculator

    Args:
        starting_storage_class: the initial storage class the objects will be stored in
        target_storage_class: the secondary storage class the objects will be stored in after transition
        total_storage_gb: the total storage in gb that will be stored
        number_objs: the total of number of objects that will be added over a month
        data_retrieved_gb: the amount of data in gb that will be retrieved from the second storage class after transition
        initial_storage_class_days: the number of days objects will exist in the initial storage class before transition
        total_days: the number of days objects will exist (theoretically before they are expired)

    Returns:
        bool, the core decision can be deduced from what is printed to the console
    """
    # Calculate the amount of days objects will be in the target storage class
    # This is based on the number of days the object exists (total_days) and the number of days
    # the object will be in the intial storage class
    target_storage_class_days = total_days - initial_storage_class_days

    # Calculate the number of months, based on input days
    initial_storage_class_months = calculate_months(initial_storage_class_days)
    total_months = calculate_months(total_days)
    target_storage_class_months = calculate_months(target_storage_class_days)

    # Calculate the storage cost for the initial storage class over the entire life of the objects
    # This is used as the baseline comparison to see if a lifecycle policy will help or hinder in spend
    storage_cost_no_transition = calculate_storage_cost(
        months=total_months,
        storage_class=starting_storage_class,
        total_storage_gb=total_storage_gb,
        number_objs=number_objs,
    )

    # Calculate the storage cost in the initial storage class, taking account for the number of months it will be in there
    inital_storage_cost = calculate_storage_cost(
        months=initial_storage_class_months,
        storage_class=starting_storage_class,
        total_storage_gb=total_storage_gb,
        number_objs=number_objs,
    )

    # Calculate the storage cost in the secondary storage class
    target_storage_cost = calculate_storage_cost(
        months=target_storage_class_months,
        storage_class=target_storage_class,
        total_storage_gb=total_storage_gb,
        number_objs=number_objs,
    )

    # Calculate the cost of the transition
    target_transition_cost = calculate_transition_cost(
        number_objs=number_objs, target_storage_class=target_storage_class
    )

    # Calculate the cost of retrieval from secondary storage class
    target_retrieval_cost = calculate_data_retrieval_cost(
        months=target_storage_class_months,
        target_storage_class=target_storage_class,
        data_retrieved_gb=data_retrieved_gb,
    )

    # Calculate the total cost of ownership in the secondary storage class
    # This includes the initial time period
    lifecycle_policy_cost = (
        inital_storage_cost
        + target_storage_cost
        + target_transition_cost
        + target_retrieval_cost
    )

    # Print some data out informing the user of cost differences
    print(
        "Cost with lifecycle policy in multiple storage classes: %f"
        % lifecycle_policy_cost
    )
    print("Cost in current storage class: %f" % storage_cost_no_transition)

    print("\n")

    if storage_cost_no_transition > lifecycle_policy_cost:
        print("Implementing this lifecycle policy will be more cost effective")
    else:
        print("Retaining data in current storage class will be more effective")

    return True


if __name__ == "__main__":
    args = ARGS
    handler(
        starting_storage_class=args.starting_storage_class,
        target_storage_class=args.target_storage_class,
        total_storage_gb=args.total_storage_gb,
        number_objs=args.number_objs,
        data_retrieved_gb=args.data_retrieved_gb,
        initial_storage_class_days=args.initial_storage_class_days,
        total_days=args.total_days,
    )