"""
Transaction resource clients (sync and async) for creating, accepting,
submitting, verifying, disputing, listing, and cancelling transactions.
"""

from __future__ import annotations

from typing import Any
from urllib.parse import quote

from ..auth import sign_accept, sign_submit, sign_dispute
from ..http import HttpClient, AsyncHttpClient
from ..models import (
    Transaction,
    TransactionVerification,
    Dispute,
    PaginatedResponse,
    PaginationInfo,
)


class TransactionsClient:
    """Synchronous client for transaction endpoints."""

    def __init__(self, http: HttpClient, private_key: str | None = None) -> None:
        self._http = http
        self._private_key = private_key

    def post(
        self,
        to_agent_id: str,
        domain: str,
        amount: str,
        description: str,
        metadata: dict[str, Any] | None = None,
    ) -> Transaction:
        """Post a new transaction between the calling agent and a target agent."""
        body: dict[str, Any] = {
            "toAgentId": to_agent_id,
            "domain": domain,
            "amount": amount,
            "description": description,
        }
        if metadata is not None:
            body["metadata"] = metadata

        data = self._http.post("/v1/transactions", body)
        return Transaction.model_validate(data)

    def get(self, transaction_id: str) -> Transaction:
        """Get the details of a specific transaction by ID."""
        data = self._http.get(f"/v1/transactions/{quote(transaction_id, safe='')}")
        return Transaction.model_validate(data)

    def accept(self, transaction_id: str) -> Transaction:
        """Accept a pending transaction. Requires a private key."""
        if not self._private_key:
            raise ValueError("A private_key is required to accept a transaction.")

        sig_data = sign_accept(self._private_key, transaction_id)
        data = self._http.post(
            f"/v1/transactions/{quote(transaction_id, safe='')}/accept",
            {
                "signature": sig_data["signature"],
                "nonce": sig_data["nonce"],
                "deadline": sig_data["deadline"],
            },
        )
        return Transaction.model_validate(data)

    def submit(
        self,
        transaction_id: str,
        rating: int,
        feedback: str | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> Transaction:
        """Submit a completed transaction with a rating. Requires a private key."""
        if not self._private_key:
            raise ValueError("A private_key is required to submit a transaction.")

        sig_data = sign_submit(
            self._private_key, transaction_id, rating, feedback or ""
        )

        body: dict[str, Any] = {
            "rating": rating,
            "signature": sig_data["signature"],
            "nonce": sig_data["nonce"],
            "deadline": sig_data["deadline"],
        }
        if feedback is not None:
            body["feedback"] = feedback
        if metadata is not None:
            body["metadata"] = metadata

        data = self._http.post(
            f"/v1/transactions/{quote(transaction_id, safe='')}/submit",
            body,
        )
        return Transaction.model_validate(data)

    def get_verification(self, transaction_id: str) -> TransactionVerification:
        """Get the on-chain verification status and Merkle proof."""
        data = self._http.get(
            f"/v1/transactions/{quote(transaction_id, safe='')}/verification"
        )
        return TransactionVerification.model_validate(data)

    def dispute(
        self,
        transaction_id: str,
        reason: str,
        evidence: dict[str, Any] | None = None,
    ) -> Dispute:
        """Dispute a transaction. Requires a private key."""
        if not self._private_key:
            raise ValueError("A private_key is required to dispute a transaction.")

        sig_data = sign_dispute(self._private_key, transaction_id, reason)

        body: dict[str, Any] = {
            "reason": reason,
            "signature": sig_data["signature"],
            "nonce": sig_data["nonce"],
            "deadline": sig_data["deadline"],
        }
        if evidence is not None:
            body["evidence"] = evidence

        data = self._http.post(
            f"/v1/transactions/{quote(transaction_id, safe='')}/dispute",
            body,
        )
        return Dispute.model_validate(data)

    def list(
        self,
        agent_id: str | None = None,
        domain: str | None = None,
        status: str | None = None,
        from_time: str | None = None,
        to_time: str | None = None,
        sort_by: str | None = None,
        sort_order: str | None = None,
        page: int | None = None,
        limit: int | None = None,
    ) -> PaginatedResponse[Transaction]:
        """List transactions with filtering, sorting, and pagination."""
        params: dict[str, Any] = {
            "agentId": agent_id,
            "domain": domain,
            "status": status,
            "from": from_time,
            "to": to_time,
            "sortBy": sort_by,
            "sortOrder": sort_order,
            "page": page,
            "limit": limit,
        }

        data = self._http.get("/v1/transactions", params)
        transactions = [Transaction.model_validate(item) for item in data["data"]]
        pagination = PaginationInfo.model_validate(data["pagination"])
        return PaginatedResponse[Transaction](data=transactions, pagination=pagination)

    def cancel(self, transaction_id: str) -> Transaction:
        """Cancel a pending transaction that has not yet been accepted."""
        data = self._http.post(
            f"/v1/transactions/{quote(transaction_id, safe='')}/cancel"
        )
        return Transaction.model_validate(data)


class AsyncTransactionsClient:
    """Asynchronous client for transaction endpoints."""

    def __init__(self, http: AsyncHttpClient, private_key: str | None = None) -> None:
        self._http = http
        self._private_key = private_key

    async def post(
        self,
        to_agent_id: str,
        domain: str,
        amount: str,
        description: str,
        metadata: dict[str, Any] | None = None,
    ) -> Transaction:
        """Post a new transaction between the calling agent and a target agent."""
        body: dict[str, Any] = {
            "toAgentId": to_agent_id,
            "domain": domain,
            "amount": amount,
            "description": description,
        }
        if metadata is not None:
            body["metadata"] = metadata

        data = await self._http.post("/v1/transactions", body)
        return Transaction.model_validate(data)

    async def get(self, transaction_id: str) -> Transaction:
        """Get the details of a specific transaction by ID."""
        data = await self._http.get(
            f"/v1/transactions/{quote(transaction_id, safe='')}"
        )
        return Transaction.model_validate(data)

    async def accept(self, transaction_id: str) -> Transaction:
        """Accept a pending transaction. Requires a private key."""
        if not self._private_key:
            raise ValueError("A private_key is required to accept a transaction.")

        sig_data = sign_accept(self._private_key, transaction_id)
        data = await self._http.post(
            f"/v1/transactions/{quote(transaction_id, safe='')}/accept",
            {
                "signature": sig_data["signature"],
                "nonce": sig_data["nonce"],
                "deadline": sig_data["deadline"],
            },
        )
        return Transaction.model_validate(data)

    async def submit(
        self,
        transaction_id: str,
        rating: int,
        feedback: str | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> Transaction:
        """Submit a completed transaction with a rating. Requires a private key."""
        if not self._private_key:
            raise ValueError("A private_key is required to submit a transaction.")

        sig_data = sign_submit(
            self._private_key, transaction_id, rating, feedback or ""
        )

        body: dict[str, Any] = {
            "rating": rating,
            "signature": sig_data["signature"],
            "nonce": sig_data["nonce"],
            "deadline": sig_data["deadline"],
        }
        if feedback is not None:
            body["feedback"] = feedback
        if metadata is not None:
            body["metadata"] = metadata

        data = await self._http.post(
            f"/v1/transactions/{quote(transaction_id, safe='')}/submit",
            body,
        )
        return Transaction.model_validate(data)

    async def get_verification(self, transaction_id: str) -> TransactionVerification:
        """Get the on-chain verification status and Merkle proof."""
        data = await self._http.get(
            f"/v1/transactions/{quote(transaction_id, safe='')}/verification"
        )
        return TransactionVerification.model_validate(data)

    async def dispute(
        self,
        transaction_id: str,
        reason: str,
        evidence: dict[str, Any] | None = None,
    ) -> Dispute:
        """Dispute a transaction. Requires a private key."""
        if not self._private_key:
            raise ValueError("A private_key is required to dispute a transaction.")

        sig_data = sign_dispute(self._private_key, transaction_id, reason)

        body: dict[str, Any] = {
            "reason": reason,
            "signature": sig_data["signature"],
            "nonce": sig_data["nonce"],
            "deadline": sig_data["deadline"],
        }
        if evidence is not None:
            body["evidence"] = evidence

        data = await self._http.post(
            f"/v1/transactions/{quote(transaction_id, safe='')}/dispute",
            body,
        )
        return Dispute.model_validate(data)

    async def list(
        self,
        agent_id: str | None = None,
        domain: str | None = None,
        status: str | None = None,
        from_time: str | None = None,
        to_time: str | None = None,
        sort_by: str | None = None,
        sort_order: str | None = None,
        page: int | None = None,
        limit: int | None = None,
    ) -> PaginatedResponse[Transaction]:
        """List transactions with filtering, sorting, and pagination."""
        params: dict[str, Any] = {
            "agentId": agent_id,
            "domain": domain,
            "status": status,
            "from": from_time,
            "to": to_time,
            "sortBy": sort_by,
            "sortOrder": sort_order,
            "page": page,
            "limit": limit,
        }

        data = await self._http.get("/v1/transactions", params)
        transactions = [Transaction.model_validate(item) for item in data["data"]]
        pagination = PaginationInfo.model_validate(data["pagination"])
        return PaginatedResponse[Transaction](data=transactions, pagination=pagination)

    async def cancel(self, transaction_id: str) -> Transaction:
        """Cancel a pending transaction that has not yet been accepted."""
        data = await self._http.post(
            f"/v1/transactions/{quote(transaction_id, safe='')}/cancel"
        )
        return Transaction.model_validate(data)
