from dataclasses import dataclass
from enum import IntEnum, nonmember
from typing import List


class RevenueRange(IntEnum):
    """매출액 가격구간 (Revenue Price Range)"""

    LOW = 0  # ≤ 1,000만원
    MID = 1  # 1,000만원 ~ 3,000만원
    HIGH = 2  # > 3,000만원

    _DESCRIPTIONS = nonmember(("Low (≤1,000만원)", "Mid (1,000~3,000만원)", "High (>3,000만원)"))
    _WEIGHTS = nonmember((0.3, 0.6, 1.0))  # 매출액이 높을수록 협상 여지 큼

    @property
    def description(self) -> str:
        return self._DESCRIPTIONS[self.value]

    @property
    def weight(self) -> float:
        return self._WEIGHTS[self.value]

    @classmethod
    def from_amount(cls, amount: float) -> "RevenueRange":
        """매출액(만원 단위)으로부터 구간 결정"""
        if amount <= 1000:
            return cls.LOW
        elif amount <= 3000:
            return cls.MID
        else:
            return cls.HIGH


class DistributionStructure(IntEnum):
    """유통 구조 (Distribution Structure)"""

    MANUFACTURER = 0  # 제조
    WHOLESALER = 1  # 총판
    RETAILER = 2  # 유통

    _DESCRIPTIONS = nonmember(("제조", "총판", "유통"))
    _WEIGHTS = nonmember((0.2, 0.5, 1.0))  # 유통 단계가 복잡할수록 협상 여지 큼

    @property
    def description(self) -> str:
        return self._DESCRIPTIONS[self.value]

    @property
    def weight(self) -> float:
        return self._WEIGHTS[self.value]

    @classmethod
    def from_code(cls, code: str) -> "DistributionStructure":
        """코드로부터 유통 구조 결정"""
        normalized = (code or "").strip().upper()
        mapping = {"M": cls.MANUFACTURER, "W": cls.WHOLESALER, "R": cls.RETAILER}
        if normalized not in mapping:
            raise ValueError(f"unknown distribution code: {code}")
        return mapping[normalized]


class PartnerType(IntEnum):
    """파트너사 종류 (Partner Type)"""

    SINGLE = 0  # 단독
    MULTIPLE = 1  # 다수
    NONE = 2  # 없음

    _DESCRIPTIONS = nonmember(("Single (단독)", "Multiple (다수)", "None (없음)"))
    _WEIGHTS = nonmember((0.5, 1.0, 0.3))  # 다수 파트너사일수록 협상 복잡도 증가

    @property
    def description(self) -> str:
        return self._DESCRIPTIONS[self.value]

    @property
    def weight(self) -> float:
        return self._WEIGHTS[self.value]

    @classmethod
    def from_count(cls, count: int) -> "PartnerType":
        """파트너사 수로부터 구간 결정"""
        if count == 0:
            return cls.NONE
        elif count == 1:
            return cls.SINGLE
        else:
            return cls.MULTIPLE


class AcceptanceRate(IntEnum):
    """가격 수용률 구간 (Price Acceptance Rate)"""

    LOW = 0  # < 30%
    MID = 1  # 30% ~ 90%
    HIGH = 2  # > 90%

    _DESCRIPTIONS = nonmember(("Low (<30%)", "Mid (30~90%)", "High (>90%)"))
    _WEIGHTS = nonmember((0.3, 0.6, 1.0))  # 수용률이 높을수록 협상 성공 가능성 높음

    @property
    def description(self) -> str:
        return self._DESCRIPTIONS[self.value]

    @property
    def weight(self) -> float:
        return self._WEIGHTS[self.value]

    @classmethod
    def from_ratio(cls, ratio: float) -> "AcceptanceRate":
        """수용률(0~1)로부터 구간 결정"""
        normalized = max(0.0, min(1.0, ratio))
        if normalized < 0.30:
            return cls.LOW
        elif normalized <= 0.90:
            return cls.MID
        else:
            return cls.HIGH


class InputPriceZone(IntEnum):
    """입력 금액 구간 (Input Price Zone)"""

    PZ1 = 0  # A ≤ P ≤ T (앵커가격 ~ 목표가격)
    PZ2 = 1  # P > T (목표가격 초과)

    _DESCRIPTIONS = nonmember(("PZ1 (A≤P≤T)", "PZ2 (P>T)"))
    _WEIGHTS = nonmember((1.0, 0.3))  # 목표가격 이내일수록 협상 유리

    @property
    def description(self) -> str:
        return self._DESCRIPTIONS[self.value]

    @property
    def weight(self) -> float:
        return self._WEIGHTS[self.value]

    @classmethod
    def from_prices(
        cls,
        input_price: float,
        anchor_price: float,
        target_price: float,
    ) -> "InputPriceZone":
        """입력가격, 앵커가격, 목표가격으로부터 구간 결정"""
        if anchor_price <= 0 or target_price <= 0:
            raise ValueError("anchor_price and target_price must be positive")
        if anchor_price > target_price:
            raise ValueError("anchor_price must not exceed target_price")

        if anchor_price <= input_price <= target_price:
            return cls.PZ1
        else:
            return cls.PZ2


@dataclass
class State:
    """
    Q-Learning State 표현 (Version 3.0 - 162 states)

    State = (매출액 가격구간, 유통 구조, 파트너사 종류, 가격 수용률 구간, 입력 금액 구간)
    Total: 3 × 3 × 3 × 3 × 2 = 162 states
    """

    revenue_range: RevenueRange
    distribution: DistributionStructure
    partner_type: PartnerType
    acceptance_rate: AcceptanceRate
    input_price_zone: InputPriceZone

    def to_array(self) -> List[int]:
        """State를 배열로 변환"""
        return [
            self.revenue_range.value,
            self.distribution.value,
            self.partner_type.value,
            self.acceptance_rate.value,
            self.input_price_zone.value,
        ]

    def to_index(self) -> int:
        """
        State를 1차원 인덱스로 변환 (Q-Table 인덱싱용)

        State space: 3 × 3 × 3 × 3 × 2 = 162

        Returns:
            0 ~ 161 사이의 인덱스
        """
        # 5D to 1D index conversion
        # index = revenue * (3*3*3*2) + dist * (3*3*2) + partner * (3*2) + accept * 2 + pricezone
        return (
            self.revenue_range.value * 54
            + self.distribution.value * 18
            + self.partner_type.value * 6
            + self.acceptance_rate.value * 2
            + self.input_price_zone.value
        )

    @classmethod
    def from_index(cls, index: int) -> "State":
        """
        1차원 인덱스에서 State 복원

        Args:
            index: 0 ~ 161 사이의 인덱스

        Returns:
            State 객체
        """
        if index < 0 or index >= 162:
            raise ValueError(f"index {index} out of range [0, 162)")

        # 1D to 5D index conversion
        revenue_value = index // 54
        remainder = index % 54

        dist_value = remainder // 18
        remainder = remainder % 18

        partner_value = remainder // 6
        remainder = remainder % 6

        accept_value = remainder // 2
        pricezone_value = remainder % 2

        return cls(
            revenue_range=RevenueRange(revenue_value),
            distribution=DistributionStructure(dist_value),
            partner_type=PartnerType(partner_value),
            acceptance_rate=AcceptanceRate(accept_value),
            input_price_zone=InputPriceZone(pricezone_value),
        )

    @classmethod
    def from_array(cls, arr: List[int]) -> "State":
        """배열로부터 State 생성"""
        if len(arr) != 5:
            raise ValueError(f"Expected 5 elements, got {len(arr)}")
        return cls(
            revenue_range=RevenueRange(arr[0]),
            distribution=DistributionStructure(arr[1]),
            partner_type=PartnerType(arr[2]),
            acceptance_rate=AcceptanceRate(arr[3]),
            input_price_zone=InputPriceZone(arr[4]),
        )

    def __str__(self) -> str:
        return (
            f"State("
            f"revenue={self.revenue_range.description}, "
            f"distribution={self.distribution.description}, "
            f"partner={self.partner_type.description}, "
            f"acceptance={self.acceptance_rate.description}, "
            f"price_zone={self.input_price_zone.description})"
        )
