from __future__ import annotations

import logging
from dataclasses import dataclass, field

from chik.full_node.fee_estimate import FeeEstimate, FeeEstimateGroup, FeeEstimateV2, fee_estimate_v2_to_v1
from chik.full_node.fee_estimation import FeeMempoolInfo
from chik.full_node.fee_tracker import (
    BucketResult,
    EstimateResult,
    FeeTracker,
    get_bucket_index,
    get_estimate_time_intervals,
)
from chik.types.fee_rate import FeeRate, FeeRateV2
from chik.util.ints import uint32, uint64


# https://github.com/bitcoin/bitcoin/blob/5b6f0f31fa6ce85db3fb7f9823b1bbb06161ae32/src/policy/fees.cpp
@dataclass()
class SmartFeeEstimator:
    fee_tracker: FeeTracker
    max_block_cost_klvm: uint64
    log: logging.Logger = field(default_factory=lambda: logging.getLogger(__name__))

    def parse(self, fee_result: EstimateResult) -> float:
        fail_bucket: BucketResult = fee_result.fail_bucket
        median = fee_result.median

        if median != -1:
            return median / 1000.0

        if fail_bucket.start == 0:
            return -1.0

        # If median is -1, tracker wasn't able to find a passing bucket.
        # Suggest one bucket higher than the lowest failing bucket.

        # get_bucket_index returns left (-1) bucket (-1). Start value is already -1
        # We want +1 from the lowest bucket it failed at. Thus +3
        max_val = len(self.fee_tracker.buckets) - 1
        start_index = min(get_bucket_index(self.fee_tracker.buckets, fail_bucket.start) + 3, max_val)

        fee_val: float = self.fee_tracker.buckets[start_index]
        return fee_val / 1000.0

    def get_estimate_for_block(self, block: uint32) -> FeeEstimateV2:
        estimate_result = self.fee_tracker.estimate_fee_for_block(block)
        return self.estimate_result_to_fee_estimate(estimate_result)

    def get_estimate(self, time_offset_seconds: int) -> FeeEstimateV2:
        estimate_result = self.fee_tracker.estimate_fee(time_offset_seconds)
        return self.estimate_result_to_fee_estimate(estimate_result)

    def get_estimates(self, info: FeeMempoolInfo, ignore_mempool: bool = False) -> FeeEstimateGroup:
        self.log.error(self.fee_tracker.buckets)
        short_time_seconds, med_time_seconds, long_time_seconds = get_estimate_time_intervals()

        if ignore_mempool is False and (self.fee_tracker.latest_seen_height == 0):
            return FeeEstimateGroup(error="Not enough data", estimates=[])

        tracking_length = self.fee_tracker.latest_seen_height - self.fee_tracker.first_recorded_height
        if tracking_length < 20:
            return FeeEstimateGroup(error="Not enough data", estimates=[])

        if ignore_mempool is False and info.current_mempool_cost < int(info.mempool_info.max_block_klvm_cost * 0.8):
            return FeeEstimateGroup(
                error=None,
                estimates=[
                    FeeEstimate(None, uint64(short_time_seconds), FeeRate(uint64(0))),
                    FeeEstimate(None, uint64(med_time_seconds), FeeRate(uint64(0))),
                    FeeEstimate(None, uint64(long_time_seconds), FeeRate(uint64(0))),
                ],
            )

        short_result, med_result, long_result = self.fee_tracker.estimate_fees()

        short = self.estimate_result_to_fee_estimate(short_result)
        med = self.estimate_result_to_fee_estimate(med_result)
        long = self.estimate_result_to_fee_estimate(long_result)
        estimates = [fee_estimate_v2_to_v1(e) for e in [short, med, long]]
        return FeeEstimateGroup(error=None, estimates=estimates)

    def estimate_result_to_fee_estimate(self, r: EstimateResult) -> FeeEstimateV2:
        fee: float = self.parse(r)
        if fee == -1:
            return FeeEstimateV2("Not enough data", r.requested_time, FeeRateV2(0))
        else:
            # convert from mojo / 1000 klvm_cost to mojo / 1 klvm_cost
            return FeeEstimateV2(None, r.requested_time, FeeRateV2(fee / 1000))
