"""Analyzes the AliExpress store behind the product."""
from __future__ import annotations

from dataclasses import dataclass
from typing import Optional

from ...constants import constants as C
from ...utils.helpers import (
    clamp,
    first_present,
    ratio_score,
    round2,
    safe_get,
    saturating_score,
    to_float,
    to_int,
    weighted_average,
)


@dataclass
class StoreAnalysis:
    store_name: str = ""
    communication_rating: float = 0.0
    shipping_rating: float = 0.0
    item_as_described_rating: float = 0.0
    total_sales: int = 0
    total_reviews: int = 0
    followers: int = 0
    score: float = 0.0
    grade: str = C.STORE_GRADES["POOR"]

    @property
    def average_rating(self) -> float:
        ratings = [r for r in (self.communication_rating, self.shipping_rating,
                               self.item_as_described_rating) if r > 0]
        return sum(ratings) / len(ratings) if ratings else 0.0

    def to_dict(self) -> dict:
        return {
            "storeName": self.store_name,
            "communicationRating": round2(self.communication_rating),
            "shippingRating": round2(self.shipping_rating),
            "itemAsDescribedRating": round2(self.item_as_described_rating),
            "averageRating": round2(self.average_rating),
            "totalSales": self.total_sales,
            "totalReviews": self.total_reviews,
            "followers": self.followers,
            "score": round(self.score, 2),
            "grade": self.grade,
        }


class StoreAnalyzer:
    """Single responsibility: score the seller.

    Ratings carry most of the weight; sales / review / follower volume adds
    proof the ratings are earned. Metrics the supplier API did not provide
    are *skipped* (not scored as zero) and the remaining weights are
    renormalized, so a store is never penalized for unavailable data.
    """

    def analyze(self, product_json: dict) -> StoreAnalysis:
        store = safe_get(product_json, "ae_store_info", {}) or {}

        result = StoreAnalysis(
            store_name=str(first_present(store, ("store_name", "shop_name"),
                                         default="")),
            communication_rating=to_float(first_present(
                store, ("communication_rating", "communication"))),
            shipping_rating=to_float(first_present(
                store, ("shipping_speed_rating", "shipping_rating", "shipping"))),
            item_as_described_rating=to_float(first_present(
                store, ("item_as_described_rating", "item_as_descriped_rating",
                        "item_as_described"))),
            total_sales=to_int(first_present(
                store, ("total_sales", "sales_count", "orders"))),
            total_reviews=to_int(first_present(
                store, ("total_reviews", "review_count", "evaluation_count"))),
            followers=to_int(first_present(
                store, ("followers", "follower_count", "wishlist_count"))),
        )

        weights = C.STORE_METRIC_WEIGHTS
        # None = metric unavailable -> excluded and weights renormalized.
        rating_score = (
            clamp((result.average_rating / 5.0) * 100.0)
            if result.average_rating > 0 else None
        )
        sales_score = (
            saturating_score(result.total_sales, C.STORE_MAX_SALES_FOR_FULL_SCORE)
            if result.total_sales > 0 else None
        )
        reviews_score = (
            saturating_score(result.total_reviews, C.STORE_MAX_REVIEWS_FOR_FULL_SCORE)
            if result.total_reviews > 0 else None
        )
        followers_score = (
            saturating_score(result.followers, C.STORE_MAX_FOLLOWERS_FOR_FULL_SCORE)
            if result.followers > 0 else None
        )
        result.score = weighted_average([
            (rating_score, weights["ratings"]),
            (sales_score, weights["sales"]),
            (reviews_score, weights["reviews"]),
            (followers_score, weights["followers"]),
        ])
        result.grade = self._grade(result.score)
        return result

    @staticmethod
    def _grade(score: float) -> str:
        if score >= C.STORE_GRADE_EXCELLENT:
            return C.STORE_GRADES["EXCELLENT"]
        if score >= C.STORE_GRADE_GOOD:
            return C.STORE_GRADES["GOOD"]
        if score >= C.STORE_GRADE_AVERAGE:
            return C.STORE_GRADES["AVERAGE"]
        return C.STORE_GRADES["POOR"]
