from abc import ABC, abstractmethod
from datetime import datetime
from typing import Dict

import numpy as np

from .assets import Asset


class FinancialDataProvider(ABC):
    @abstractmethod
    def get_asset_value(self, asset: Asset, currency: str) -> float:
        """Return the current value of the asset in specified currency."""
        ...

    @abstractmethod
    def get_asset_aggregate_cashflows(self, asset: Asset, start: datetime, end: datetime, currency: str) -> float:
        """Return the expected sum of the cashflows generated by the Asset between start and end, in
        specified currency."""
        ...


class FinancialModelBase(ABC):
    @abstractmethod
    def damage_to_loss(self, asset: Asset, impact: np.ndarray, currency: str):
        """Convert the fractional damage of the specified asset to a financial loss."""
        ...

    @abstractmethod
    def disruption_to_loss(self, asset: Asset, impact: np.ndarray, year: int, currency: str):
        """Convert the fractional annual disruption of the specified asset to a financial loss."""
        ...


class FinancialModel(FinancialModelBase):
    """ "Financial Model using a FinancialDataProvider as source of information."""

    def __init__(self, data_provider: FinancialDataProvider):
        self.data_provider = data_provider

    def damage_to_loss(self, asset: Asset, impact: np.ndarray, currency: str):
        return self.data_provider.get_asset_value(asset, currency) * impact

    def disruption_to_loss(self, asset: Asset, impact: np.ndarray, year: int, currency: str):
        return (
            self.data_provider.get_asset_aggregate_cashflows(
                asset, datetime(year, 1, 1), datetime(year, 12, 31), currency
            )
            * impact
        )


class CompositeFinancialModel(FinancialModelBase):
    """Financial model split by asset type."""

    def __init__(self, financial_models: Dict[type, FinancialModelBase]):
        self.financial_models = financial_models

    def damage_to_loss(self, asset: Asset, impact: np.ndarray, currency: str):
        return self.financial_models[type(asset)].damage_to_loss(asset, impact, currency)

    def disruption_to_loss(self, asset: Asset, impact: np.ndarray, year: int, currency: str):
        return self.financial_models[type(asset)].disruption_to_loss(asset, impact, year, currency)
