from __future__ import annotations
from typing import Dict, Any
from .eip712_struct import EIP712Struct
from eth_abi import encode as abi_encode
from eth_utils import keccak, to_checksum_address


class EIP712OrderParameters(EIP712Struct):
    srcAsset: str
    dstAsset: str
    srcQuantity: int
    dstQuantity: int
    minQuantity: int
    darkSalt: int

    SOL_TYPE_NAME = "OrderParameters"
    TYPE_STRING = "OrderParameters(address srcAsset,address dstAsset,uint256 srcQuantity,uint256 dstQuantity,uint256 minQuantity,uint128 darkSalt)"
    TYPE_STRUCT = [
        {"name": "srcAsset", "type": "address"},
        {"name": "dstAsset", "type": "address"},
        {"name": "srcQuantity", "type": "uint256"},
        {"name": "dstQuantity", "type": "uint256"},
        {"name": "minQuantity", "type": "uint256"},
        {"name": "darkSalt", "type": "uint128"},
    ]

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "EIP712OrderParameters":
        kwargs = {}

        if "srcAsset" in data:
            kwargs["srcAsset"] = to_checksum_address(data["srcAsset"])

        if "dstAsset" in data:
            kwargs["dstAsset"] = to_checksum_address(data["dstAsset"])

        if "srcQuantity" in data:
            kwargs["srcQuantity"] = int(data["srcQuantity"])

        if "dstQuantity" in data:
            kwargs["dstQuantity"] = int(data["dstQuantity"])

        if "minQuantity" in data:
            kwargs["minQuantity"] = int(data["minQuantity"])

        if "darkSalt" in data:
            kwargs["darkSalt"] = int(data["darkSalt"])

        return cls(**kwargs)

    def hash(self) -> bytes:
        return keccak(
            abi_encode(
                [
                    "bytes32",
                    "address",
                    "address",
                    "uint256",
                    "uint256",
                    "uint256",
                    "uint128",
                ],
                [
                    self.type_hash,
                    to_checksum_address(self.srcAsset),
                    to_checksum_address(self.dstAsset),
                    int(self.srcQuantity),
                    int(self.dstQuantity),
                    int(self.minQuantity),
                    int(self.darkSalt),
                ],
            )
        )


class EIP712Domain(EIP712Struct):
    SOL_TYPE_NAME = "EIP712Domain"

    name: str
    version: str
    chainId: int
    verifyingContract: str

    TYPE_STRING = "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
    TYPE_STRUCT = [
        {"name": "name", "type": "string"},
        {"name": "version", "type": "string"},
        {"name": "chainId", "type": "uint256"},
        {"name": "verifyingContract", "type": "address"},
    ]

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "EIP712Domain":
        kwargs = {
            "name": data.get("name", ""),
            "version": str(data.get("version", "1")),
            "chainId": int(data.get("chainId", 0)),
            "verifyingContract": to_checksum_address(
                data.get(
                    "verifyingContract",
                    "0x0000000000000000000000000000000000000000",
                )
            ),
        }
        return cls(**kwargs)

    def hash(self) -> bytes:
        return keccak(
            abi_encode(
                ["bytes32", "bytes32", "bytes32", "uint256", "address"],
                [
                    self.type_hash,
                    keccak(text=self.name),
                    keccak(text=self.version),
                    int(self.chainId),
                    to_checksum_address(self.verifyingContract),
                ],
            )
        )


class EIP712Permit(EIP712Struct):
    SOL_TYPE_NAME = "Permit"

    owner: str
    spender: str
    value: int
    nonce: int
    deadline: int

    TYPE_STRING = "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
    TYPE_STRUCT = [
        {"name": "owner", "type": "address"},
        {"name": "spender", "type": "address"},
        {"name": "value", "type": "uint256"},
        {"name": "nonce", "type": "uint256"},
        {"name": "deadline", "type": "uint256"},
    ]

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "EIP712Permit":
        kwargs = {}
        if "owner" in data:
            kwargs["owner"] = to_checksum_address(data["owner"])
        if "spender" in data:
            kwargs["spender"] = to_checksum_address(data["spender"])
        if "value" in data:
            kwargs["value"] = int(data["value"])
        if "nonce" in data:
            kwargs["nonce"] = int(data["nonce"])
        if "deadline" in data:
            kwargs["deadline"] = int(data["deadline"])
        return cls(**kwargs)

    def hash(self) -> bytes:
        return keccak(
            abi_encode(
                [
                    "bytes32",
                    "address",
                    "address",
                    "uint256",
                    "uint256",
                    "uint256",
                ],
                [
                    self.type_hash,
                    to_checksum_address(self.owner),
                    to_checksum_address(self.spender),
                    int(self.value),
                    int(self.nonce),
                    int(self.deadline),
                ],
            )
        )


class EIP712TokenPermissions(EIP712Struct):
    SOL_TYPE_NAME = "TokenPermissions"

    token: str
    amount: int

    TYPE_STRING = "TokenPermissions(address token,uint256 amount)"
    TYPE_STRUCT = [
        {"name": "token", "type": "address"},
        {"name": "amount", "type": "uint256"},
    ]

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "EIP712TokenPermissions":
        kwargs = {}

        if "token" in data:
            kwargs["token"] = to_checksum_address(data["token"])

        if "amount" in data:
            kwargs["amount"] = int(data["amount"])

        return cls(**kwargs)

    def hash(self):
        return keccak(
            abi_encode(
                ["bytes32", "address", "uint256"],
                [self.type_hash, to_checksum_address(self.token), int(self.amount)],
            )
        )


class EIP712MMWitness(EIP712Struct):
    SOL_TYPE_NAME = "MMWitness"

    mmToken: str
    amount: int
    orderId: bytes

    TYPE_STRING = "MMWitness(address mmToken,uint256 amount,bytes32 orderId)"
    TYPE_STRUCT = [
        {"name": "mmToken", "type": "address"},
        {"name": "amount", "type": "uint256"},
        {"name": "orderId", "type": "bytes32"},
    ]

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "EIP712MMWitness":
        kwargs = {}

        if "mmToken" in data:
            kwargs["mmToken"] = to_checksum_address(data["mmToken"])

        if "amount" in data:
            kwargs["amount"] = int(data["amount"])

        if "orderId" in data:
            order_id = data["orderId"]
            if isinstance(order_id, str):
                if order_id.startswith("0x"):
                    kwargs["orderId"] = bytes.fromhex(order_id[2:])
                else:
                    kwargs["orderId"] = bytes.fromhex(order_id)
            else:
                kwargs["orderId"] = bytes(order_id)

        return cls(**kwargs)

    def hash(self):
        order_id_bytes = self.orderId
        if len(order_id_bytes) < 32:
            order_id_bytes = b"\x00" * (32 - len(order_id_bytes)) + order_id_bytes
        elif len(order_id_bytes) > 32:
            order_id_bytes = order_id_bytes[-32:]

        return keccak(
            abi_encode(
                ["bytes32", "address", "uint256", "bytes32"],
                [
                    self.type_hash,
                    to_checksum_address(self.mmToken),
                    int(self.amount),
                    order_id_bytes,
                ],
            )
        )


class EIP712MarginWitness(EIP712Struct):
    SOL_TYPE_NAME = "MarginWitness"

    orderId: bytes
    amount: int
    loanToken: str

    TYPE_STRING = "MarginWitness(bytes32 orderId,uint256 amount,address loanToken)"
    TYPE_STRUCT = [
        {"name": "orderId", "type": "bytes32"},
        {"name": "amount", "type": "uint256"},
        {"name": "loanToken", "type": "address"},
    ]

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "EIP712MarginWitness":
        kwargs = {}

        if "orderId" in data:
            order_id = data["orderId"]
            if isinstance(order_id, str):
                if order_id.startswith("0x"):
                    kwargs["orderId"] = bytes.fromhex(order_id[2:])
                else:
                    kwargs["orderId"] = bytes.fromhex(order_id)
            else:
                kwargs["orderId"] = bytes(order_id)

        if "amount" in data:
            kwargs["amount"] = int(data["amount"])

        if "loanToken" in data:
            kwargs["loanToken"] = to_checksum_address(data["loanToken"])

        return cls(**kwargs)

    def hash(self):
        order_id_bytes = self.orderId
        if len(order_id_bytes) < 32:
            order_id_bytes = b"\x00" * (32 - len(order_id_bytes)) + order_id_bytes
        elif len(order_id_bytes) > 32:
            order_id_bytes = order_id_bytes[-32:]

        return keccak(
            abi_encode(
                ["bytes32", "bytes32", "uint256", "address"],
                [
                    self.type_hash,
                    order_id_bytes,
                    int(self.amount),
                    to_checksum_address(self.loanToken),
                ],
            )
        )


class EIP712CloseWithSwap(EIP712Struct):
    SOL_TYPE_NAME = "CloseWithSwap"

    positionId: int
    cashSettle: bool
    fractionBps: int = 10_000
    authorized: str = "0x0000000000000000000000000000000000000000"
    nonce: int = 0
    deadline: int

    TYPE_STRING = "CloseWithSwap(uint128 positionId,bool cashSettle,uint256 fractionBps,address authorized,uint256 nonce,uint256 deadline)"
    TYPE_STRUCT = [
        {"name": "positionId", "type": "uint128"},
        {"name": "cashSettle", "type": "bool"},
        {"name": "fractionBps", "type": "uint256"},
        {"name": "authorized", "type": "address"},
        {"name": "nonce", "type": "uint256"},
        {"name": "deadline", "type": "uint256"},
    ]

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "EIP712CloseWithSwap":
        kwargs = {}

        if "positionId" in data:
            kwargs["positionId"] = int(data["positionId"])

        if "cashSettle" in data:
            kwargs["cashSettle"] = bool(data["cashSettle"])

        kwargs["fractionBps"] = int(data.get("fractionBps", 10_000))

        if "authorized" in data:
            kwargs["authorized"] = to_checksum_address(data["authorized"])
        else:
            kwargs["authorized"] = "0x0000000000000000000000000000000000000000"

        if "nonce" in data:
            kwargs["nonce"] = int(data["nonce"])
        else:
            kwargs["nonce"] = 0

        if "deadline" in data:
            kwargs["deadline"] = int(data["deadline"])

        return cls(**kwargs)

    def hash(self) -> bytes:
        return keccak(
            abi_encode(
                [
                    "bytes32",
                    "uint128",
                    "bool",
                    "uint256",
                    "address",
                    "uint256",
                    "uint256",
                ],
                [
                    self.type_hash,
                    int(self.positionId),
                    bool(self.cashSettle),
                    int(self.fractionBps),
                    to_checksum_address(self.authorized),
                    int(self.nonce),
                    int(self.deadline),
                ],
            )
        )
