"""
Synor Governance SDK Client.
Proposals, voting, DAOs, and vesting.
"""

import asyncio
from typing import Optional, List, Dict, Any
from urllib.parse import urlencode, quote

import httpx

from .types import (
    GovernanceConfig,
    ProposalDraft,
    Proposal,
    ProposalFilter,
    ProposalAction,
    ProposalStatus,
    ProposalType,
    Vote,
    VoteReceipt,
    VoteChoice,
    DelegationReceipt,
    VotingPower,
    DelegationInfo,
    DaoConfig,
    Dao,
    DaoType,
    VestingSchedule,
    VestingContract,
    VestingStatus,
    ClaimResult,
    GovernanceTransaction,
)


class GovernanceError(Exception):
    """Governance SDK Error."""

    def __init__(self, message: str, code: Optional[str] = None, status_code: int = 0):
        super().__init__(message)
        self.code = code
        self.status_code = status_code


class SynorGovernance:
    """
    Synor Governance SDK client for Python.
    Proposals, voting, DAOs, and vesting.
    """

    def __init__(self, config: GovernanceConfig):
        self._config = config
        self._closed = False
        self._client = httpx.AsyncClient(
            base_url=config.endpoint,
            timeout=config.timeout,
            headers={
                "Authorization": f"Bearer {config.api_key}",
                "Content-Type": "application/json",
                "X-SDK-Version": "python/0.1.0",
            },
        )

    # ==================== Proposal Operations ====================

    async def create_proposal(self, draft: ProposalDraft) -> Proposal:
        """Create a new proposal."""
        body = self._draft_to_dict(draft)
        response = await self._post("/proposals", body)
        return self._parse_proposal(response)

    async def get_proposal(self, proposal_id: str) -> Proposal:
        """Get a proposal by ID."""
        response = await self._get(f"/proposals/{quote(proposal_id)}")
        return self._parse_proposal(response)

    async def list_proposals(self, filter: Optional[ProposalFilter] = None) -> List[Proposal]:
        """List proposals with optional filtering."""
        params = {}
        if filter:
            if filter.status:
                params["status"] = filter.status.value
            if filter.type:
                params["type"] = filter.type.value
            if filter.proposer:
                params["proposer"] = filter.proposer
            if filter.limit:
                params["limit"] = str(filter.limit)
            if filter.offset:
                params["offset"] = str(filter.offset)
        query = f"?{urlencode(params)}" if params else ""
        response = await self._get(f"/proposals{query}")
        return [self._parse_proposal(p) for p in response.get("proposals", [])]

    async def cancel_proposal(self, proposal_id: str) -> Proposal:
        """Cancel a proposal."""
        response = await self._post(f"/proposals/{quote(proposal_id)}/cancel", {})
        return self._parse_proposal(response)

    async def execute_proposal(self, proposal_id: str) -> GovernanceTransaction:
        """Execute a passed proposal."""
        response = await self._post(f"/proposals/{quote(proposal_id)}/execute", {})
        return GovernanceTransaction(
            tx_hash=response["tx_hash"],
            timestamp=response["timestamp"],
            block_number=response["block_number"],
            status=response["status"],
        )

    # ==================== Voting Operations ====================

    async def vote(
        self, proposal_id: str, vote: Vote, weight: Optional[str] = None
    ) -> VoteReceipt:
        """Vote on a proposal."""
        body: Dict[str, Any] = {"choice": vote.choice.value}
        if vote.reason:
            body["reason"] = vote.reason
        if weight:
            body["weight"] = weight
        response = await self._post(f"/proposals/{quote(proposal_id)}/vote", body)
        return self._parse_vote_receipt(response)

    async def get_votes(
        self,
        proposal_id: str,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
    ) -> List[VoteReceipt]:
        """Get votes for a proposal."""
        params = {}
        if limit:
            params["limit"] = str(limit)
        if offset:
            params["offset"] = str(offset)
        query = f"?{urlencode(params)}" if params else ""
        response = await self._get(f"/proposals/{quote(proposal_id)}/votes{query}")
        return [self._parse_vote_receipt(v) for v in response.get("votes", [])]

    async def delegate(
        self, to: str, amount: Optional[str] = None
    ) -> DelegationReceipt:
        """Delegate voting power to another address."""
        body: Dict[str, Any] = {"to": to}
        if amount:
            body["amount"] = amount
        response = await self._post("/voting/delegate", body)
        return self._parse_delegation_receipt(response)

    async def undelegate(self, from_addr: Optional[str] = None) -> DelegationReceipt:
        """Undelegate voting power."""
        body: Dict[str, Any] = {}
        if from_addr:
            body["from"] = from_addr
        response = await self._post("/voting/undelegate", body)
        return self._parse_delegation_receipt(response)

    async def get_voting_power(self, address: str) -> VotingPower:
        """Get voting power for an address."""
        response = await self._get(f"/voting/power/{quote(address)}")
        return self._parse_voting_power(response)

    async def get_my_voting_power(self) -> VotingPower:
        """Get current voting power for the authenticated user."""
        response = await self._get("/voting/power")
        return self._parse_voting_power(response)

    # ==================== DAO Operations ====================

    async def create_dao(self, config: DaoConfig) -> Dao:
        """Create a new DAO."""
        body = self._dao_config_to_dict(config)
        response = await self._post("/daos", body)
        return self._parse_dao(response)

    async def get_dao(self, dao_id: str) -> Dao:
        """Get a DAO by ID."""
        response = await self._get(f"/daos/{quote(dao_id)}")
        return self._parse_dao(response)

    async def list_daos(
        self, limit: Optional[int] = None, offset: Optional[int] = None
    ) -> List[Dao]:
        """List DAOs."""
        params = {}
        if limit:
            params["limit"] = str(limit)
        if offset:
            params["offset"] = str(offset)
        query = f"?{urlencode(params)}" if params else ""
        response = await self._get(f"/daos{query}")
        return [self._parse_dao(d) for d in response.get("daos", [])]

    async def get_dao_members(
        self,
        dao_id: str,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
    ) -> List[Dict[str, str]]:
        """Get DAO members."""
        params = {}
        if limit:
            params["limit"] = str(limit)
        if offset:
            params["offset"] = str(offset)
        query = f"?{urlencode(params)}" if params else ""
        response = await self._get(f"/daos/{quote(dao_id)}/members{query}")
        return response.get("members", [])

    # ==================== Vesting Operations ====================

    async def create_vesting_schedule(self, schedule: VestingSchedule) -> VestingContract:
        """Create a vesting schedule."""
        body = {
            "beneficiary": schedule.beneficiary,
            "total_amount": schedule.total_amount,
            "start_time": schedule.start_time,
            "cliff_duration": schedule.cliff_duration,
            "vesting_duration": schedule.vesting_duration,
            "revocable": schedule.revocable,
        }
        response = await self._post("/vesting", body)
        return self._parse_vesting_contract(response)

    async def get_vesting_contract(self, contract_id: str) -> VestingContract:
        """Get a vesting contract."""
        response = await self._get(f"/vesting/{quote(contract_id)}")
        return self._parse_vesting_contract(response)

    async def list_vesting_contracts(
        self, beneficiary: Optional[str] = None
    ) -> List[VestingContract]:
        """List vesting contracts."""
        query = f"?beneficiary={quote(beneficiary)}" if beneficiary else ""
        response = await self._get(f"/vesting{query}")
        return [self._parse_vesting_contract(c) for c in response.get("contracts", [])]

    async def claim_vested(self, contract_id: str) -> ClaimResult:
        """Claim vested tokens."""
        response = await self._post(f"/vesting/{quote(contract_id)}/claim", {})
        return ClaimResult(
            vesting_id=response["vesting_id"],
            amount=response["amount"],
            recipient=response["recipient"],
            timestamp=response["timestamp"],
            tx_hash=response["tx_hash"],
        )

    async def revoke_vesting(self, contract_id: str) -> VestingContract:
        """Revoke a vesting contract (if revocable)."""
        response = await self._post(f"/vesting/{quote(contract_id)}/revoke", {})
        return self._parse_vesting_contract(response)

    async def get_claimable_amount(self, contract_id: str) -> Dict[str, str]:
        """Get claimable amount for a vesting contract."""
        return await self._get(f"/vesting/{quote(contract_id)}/claimable")

    # ==================== Lifecycle ====================

    async def health_check(self) -> bool:
        """Health check."""
        try:
            response = await self._get("/health")
            return response.get("status") == "healthy"
        except Exception:
            return False

    @property
    def is_closed(self) -> bool:
        """Check if client is closed."""
        return self._closed

    async def close(self) -> None:
        """Close the client."""
        self._closed = True
        await self._client.aclose()

    async def __aenter__(self) -> "SynorGovernance":
        return self

    async def __aexit__(self, *args) -> None:
        await self.close()

    # ==================== Private Methods ====================

    async def _get(self, path: str) -> Dict[str, Any]:
        return await self._execute(lambda: self._client.get(path))

    async def _post(self, path: str, body: Dict[str, Any]) -> Dict[str, Any]:
        return await self._execute(lambda: self._client.post(path, json=body))

    async def _execute(self, operation) -> Dict[str, Any]:
        if self._closed:
            raise GovernanceError("Client has been closed")

        last_error = None
        for attempt in range(self._config.retries):
            try:
                response = await operation()
                self._ensure_success(response)
                if response.content:
                    return response.json()
                return {}
            except Exception as e:
                last_error = e
                if self._config.debug:
                    print(f"Attempt {attempt + 1} failed: {e}")
                if attempt < self._config.retries - 1:
                    await asyncio.sleep(2**attempt)
        raise last_error

    def _ensure_success(self, response: httpx.Response) -> None:
        if response.status_code >= 400:
            message = f"HTTP {response.status_code}"
            code = None
            try:
                error = response.json()
                message = error.get("message", message)
                code = error.get("code")
            except Exception:
                pass
            raise GovernanceError(message, code, response.status_code)

    # ==================== Parsing Methods ====================

    def _draft_to_dict(self, draft: ProposalDraft) -> Dict[str, Any]:
        result: Dict[str, Any] = {
            "title": draft.title,
            "description": draft.description,
            "type": draft.type.value,
        }
        if draft.actions:
            result["actions"] = [
                {
                    "target": a.target,
                    "value": a.value,
                    "calldata": a.calldata,
                    "description": a.description,
                }
                for a in draft.actions
            ]
        if draft.start_time:
            result["start_time"] = draft.start_time
        if draft.end_time:
            result["end_time"] = draft.end_time
        if draft.quorum:
            result["quorum"] = draft.quorum
        if draft.threshold:
            result["threshold"] = draft.threshold
        return result

    def _dao_config_to_dict(self, config: DaoConfig) -> Dict[str, Any]:
        result: Dict[str, Any] = {
            "name": config.name,
            "type": config.type.value,
            "voting_period": config.voting_period,
            "quorum": config.quorum,
            "proposal_threshold": config.proposal_threshold,
        }
        if config.token_address:
            result["token_address"] = config.token_address
        if config.signers:
            result["signers"] = config.signers
        if config.threshold:
            result["threshold"] = config.threshold
        if config.timelock_delay:
            result["timelock_delay"] = config.timelock_delay
        return result

    def _parse_proposal(self, data: Dict[str, Any]) -> Proposal:
        return Proposal(
            id=data["id"],
            title=data["title"],
            description=data["description"],
            type=ProposalType(data["type"]),
            status=ProposalStatus(data["status"]),
            proposer=data["proposer"],
            actions=[
                ProposalAction(
                    target=a["target"],
                    value=a["value"],
                    calldata=a["calldata"],
                    description=a.get("description"),
                )
                for a in data.get("actions", [])
            ],
            start_time=data["start_time"],
            end_time=data["end_time"],
            quorum=data["quorum"],
            threshold=data["threshold"],
            for_votes=data["for_votes"],
            against_votes=data["against_votes"],
            abstain_votes=data["abstain_votes"],
            total_votes=data["total_votes"],
            created_at=data["created_at"],
            executed_at=data.get("executed_at"),
            execution_tx_hash=data.get("execution_tx_hash"),
        )

    def _parse_vote_receipt(self, data: Dict[str, Any]) -> VoteReceipt:
        return VoteReceipt(
            proposal_id=data["proposal_id"],
            voter=data["voter"],
            choice=VoteChoice(data["choice"]),
            weight=data["weight"],
            timestamp=data["timestamp"],
            tx_hash=data["tx_hash"],
            reason=data.get("reason"),
        )

    def _parse_delegation_receipt(self, data: Dict[str, Any]) -> DelegationReceipt:
        return DelegationReceipt(
            delegator=data["delegator"],
            delegatee=data["delegatee"],
            amount=data["amount"],
            timestamp=data["timestamp"],
            tx_hash=data["tx_hash"],
        )

    def _parse_voting_power(self, data: Dict[str, Any]) -> VotingPower:
        delegated_to = None
        if data.get("delegated_to"):
            delegated_to = DelegationInfo(
                address=data["delegated_to"]["address"],
                amount=data["delegated_to"]["amount"],
                timestamp=data["delegated_to"]["timestamp"],
            )
        return VotingPower(
            address=data["address"],
            own_power=data["own_power"],
            delegated_power=data["delegated_power"],
            total_power=data["total_power"],
            delegated_from=[
                DelegationInfo(
                    address=d["address"],
                    amount=d["amount"],
                    timestamp=d["timestamp"],
                )
                for d in data.get("delegated_from", [])
            ],
            delegated_to=delegated_to,
        )

    def _parse_dao(self, data: Dict[str, Any]) -> Dao:
        return Dao(
            id=data["id"],
            name=data["name"],
            type=DaoType(data["type"]),
            voting_period=data["voting_period"],
            quorum=data["quorum"],
            proposal_threshold=data["proposal_threshold"],
            treasury=data["treasury"],
            total_proposals=data["total_proposals"],
            active_proposals=data["active_proposals"],
            total_members=data["total_members"],
            created_at=data["created_at"],
            token_address=data.get("token_address"),
            signers=data.get("signers"),
            threshold=data.get("threshold"),
            timelock_delay=data.get("timelock_delay"),
        )

    def _parse_vesting_contract(self, data: Dict[str, Any]) -> VestingContract:
        return VestingContract(
            id=data["id"],
            beneficiary=data["beneficiary"],
            total_amount=data["total_amount"],
            released_amount=data["released_amount"],
            vested_amount=data["vested_amount"],
            start_time=data["start_time"],
            cliff_time=data["cliff_time"],
            end_time=data["end_time"],
            revocable=data["revocable"],
            status=VestingStatus(data["status"]),
            created_at=data["created_at"],
            tx_hash=data["tx_hash"],
        )
