import json
import logging
from datetime import datetime
from typing import Iterable

import pytz

from fetchfox.apis import bookio
from fetchfox.apis.evm import moralisio, ensideascom, openseaio
from fetchfox.blockchains.base import Blockchain
from fetchfox.constants.blockchains import ETHEREUM
from fetchfox.constants.marketplaces import OPENSEA_IO
from fetchfox.dtos import AssetDTO, CampaignDTO, HoldingDTO, ListingDTO, SaleDTO
from fetchfox.helpers import formatters
from . import utils

logger = logging.getLogger(__name__)


class Evm(Blockchain):
    def __init__(
        self,
        name: str,
        currency: str,
        moralisio_api_key: str = None,
        openseaio_api_key: str = None,
    ):
        super().__init__(name, currency)

        self.moralisio_api_key: str = moralisio_api_key
        self.openseaio_api_key: str = openseaio_api_key

    def get_assets(
        self,
        collection_id: str,
        fetch_metadata: bool = True,
        *args,
        **kwargs,
    ) -> Iterable[AssetDTO]:
        if fetch_metadata:
            asset_id = -1

            while True:
                try:
                    asset_id += 1

                    yield self.get_asset(
                        collection_id=collection_id,
                        asset_id=str(asset_id),
                    )
                except ValueError:
                    raise
                except:
                    break
        else:
            assets = moralisio.get_assets(
                contract_address=collection_id,
                blockchain=self.name,
                api_key=self.moralisio_api_key,
            )

            for asset_id in assets:
                yield AssetDTO(
                    collection_id=collection_id,
                    asset_id=asset_id,
                    metadata={},
                )

    def get_asset(self, collection_id: str, asset_id: str, *args, **kwargs) -> AssetDTO:
        data = moralisio.get_asset_data(
            contract_address=collection_id,
            asset_id=asset_id,
            blockchain=self.name,
            api_key=self.moralisio_api_key,
        )

        metadata = json.loads(data["metadata"])
        metadata["attributes"].update(metadata["extraAttributes"])

        return AssetDTO(
            collection_id=collection_id,
            asset_id=asset_id,
            metadata=metadata,
        )

    def get_campaigns(self) -> Iterable[CampaignDTO]:
        for campaign in bookio.campaigns():
            if campaign["blockchain"] != "evm":
                continue

            if self.name == ETHEREUM:
                network = "mainnet"
            else:
                network = self.name

            if campaign["network"] != network:
                continue

            landing = campaign["landing"]
            platform = campaign["platform"]
            start_at = campaign["start_at"]

            yield CampaignDTO(
                blockchain=self.name,
                parlamint_id=campaign["id"],
                collection_id=platform["ThirdWeb"]["contract_address"],
                name=campaign["display_name"],
                description=campaign["landing"]["opener"],
                price=landing["price"],
                supply=campaign["total_deas"],
                limit=campaign["max_quantity"],
                pricing=landing["price_description"],
                rarity_chart_url=landing["rarity_chart_url"],
                start_at=formatters.timestamp(start_at["start_at"]),
            )

    def get_holdings(self, wallet: str, *args, **kwargs) -> Iterable[HoldingDTO]:
        if utils.is_ens_domain(wallet):
            wallet = ensideascom.resolve_ens_domain(wallet)

        holdings = moralisio.get_holdings(
            wallet,
            blockchain=self.name,
            api_key=self.moralisio_api_key,
        )

        for token in holdings:
            contract_address = token["token_address"]
            asset_id = token["token_id"]
            quantity = int(token["amount"])

            yield HoldingDTO(
                collection_id=contract_address,
                asset_id=asset_id,
                address=wallet,
                quantity=quantity,
            )

    def get_snapshot(self, collection_id: str, *args, **kwargs) -> Iterable[HoldingDTO]:
        assets = moralisio.get_owners(
            collection_id,
            blockchain=self.name,
            api_key=self.moralisio_api_key,
        )

        for asset in assets:
            yield HoldingDTO(
                collection_id=asset["contract_address"],
                asset_id=asset["asset_id"],
                address=asset["address"],
                quantity=asset["amount"],
            )

    def get_listings(
        self, collection_id: str, slug: str = None, *args, **kwargs
    ) -> Iterable[ListingDTO]:
        listings = openseaio.get_listings(
            collection_id,
            api_key=self.openseaio_api_key,
            slug=slug,
        )

        for listing in listings:
            protocol_data = listing["protocol_data"]
            parameters = protocol_data["parameters"]

            price = listing["price"]["current"]

            asset_ids = []
            asset_names = []

            for offer in parameters["offer"]:
                if offer["token"].lower() != collection_id.lower():
                    continue

                asset_ids.append(offer["identifierOrCriteria"])

            if listing.get("eventTimestamp"):
                listed_at = datetime.utcfromtimestamp(parameters["startTime"])
            else:
                listed_at = datetime.now(tz=pytz.utc)

            yield ListingDTO(
                identifier=listing["order_hash"],
                collection_id=collection_id,
                asset_ids=asset_ids,
                asset_names=asset_names,
                listing_id=listing["order_hash"],
                marketplace=OPENSEA_IO,
                price=float(int(price["value"]) / 10 ** price["decimals"]),
                currency=price["currency"].replace("WETH", "ETH"),
                listed_at=listed_at,
                listed_by=parameters["offerer"],
                tx_hash=listing["order_hash"],
            )

    def get_sales(
        self, collection_id: str, slug: str = None, *args, **kwargs
    ) -> Iterable[SaleDTO]:
        sales = openseaio.get_sales(
            collection_id,
            api_key=self.openseaio_api_key,
            slug=slug,
        )

        for sale in sales:
            asset = sale["asset"]

            asset_ids = [asset["token_id"]]
            asset_names = [asset["name"]]

            if sale.get("event_timestamp"):
                confirmed_at = datetime.fromisoformat(
                    sale["event_timestamp"] + "+00:00"
                )
            else:
                confirmed_at = datetime.now(tz=pytz.utc)

            yield SaleDTO(
                identifier=sale["id"],
                collection_id=collection_id,
                asset_ids=asset_ids,
                asset_names=asset_names,
                tx_hash=sale["transaction"]["transaction_hash"],
                marketplace=OPENSEA_IO,
                price=float(
                    int(sale["total_price"]) / 10 ** sale["payment_token"]["decimals"]
                ),
                currency=sale["payment_token"]["symbol"].replace("WETH", "ETH"),
                confirmed_at=confirmed_at,
                sold_by=sale["transaction"]["from_account"]["address"],
                bought_by=sale["transaction"]["to_account"]["address"],
                sale_id=sale["id"],
            )
