"""Vote Program Instructions."""

from enum import IntEnum
from typing import NamedTuple

from construct import Bytes, Struct, Switch, Int8ul, Int32ul, Pass  # type: ignore

from solana.publickey import PublicKey
from solana.sysvar import SYSVAR_CLOCK_PUBKEY, SYSVAR_RENT_PUBKEY
from solana.transaction import AccountMeta, TransactionInstruction

from vote.constants import VOTE_PROGRAM_ID

PUBLIC_KEY_LAYOUT = Bytes(32)


class InitializeParams(NamedTuple):
    """Initialize vote account params."""

    vote: PublicKey
    """`[w]` Uninitialized vote account"""
    rent_sysvar: PublicKey
    """`[]` Rent sysvar."""
    clock_sysvar: PublicKey
    """`[]` Clock sysvar."""
    node: PublicKey
    """`[s]` New validator identity."""

    authorized_voter: PublicKey
    """The authorized voter for this vote account."""
    authorized_withdrawer: PublicKey
    """The authorized withdrawer for this vote account."""
    commission: int
    """Commission, represented as a percentage"""


class InstructionType(IntEnum):
    """Vote Instruction Types."""

    INITIALIZE = 0
    AUTHORIZE = 1
    VOTE = 2
    WITHDRAW = 3
    UPDATE_VALIDATOR_IDENTITY = 4
    UPDATE_COMMISSION = 5
    VOTE_SWITCH = 6
    AUTHORIZE_CHECKED = 7


INITIALIZE_LAYOUT = Struct(
    "node" / PUBLIC_KEY_LAYOUT,
    "authorized_voter" / PUBLIC_KEY_LAYOUT,
    "authorized_withdrawer" / PUBLIC_KEY_LAYOUT,
    "commission" / Int8ul,
)

INSTRUCTIONS_LAYOUT = Struct(
    "instruction_type" / Int32ul,
    "args"
    / Switch(
        lambda this: this.instruction_type,
        {
            InstructionType.INITIALIZE: INITIALIZE_LAYOUT,
            InstructionType.AUTHORIZE: Pass,  # TODO
            InstructionType.VOTE: Pass,  # TODO
            InstructionType.WITHDRAW: Pass,  # TODO
            InstructionType.UPDATE_VALIDATOR_IDENTITY: Pass,  # TODO
            InstructionType.UPDATE_COMMISSION: Pass,  # TODO
            InstructionType.VOTE_SWITCH: Pass,  # TODO
            InstructionType.AUTHORIZE_CHECKED: Pass,  # TODO
        },
    ),
)


def initialize(params: InitializeParams) -> TransactionInstruction:
    """Creates a transaction instruction to initialize a new stake."""
    data = INSTRUCTIONS_LAYOUT.build(
        dict(
            instruction_type=InstructionType.INITIALIZE,
            args=dict(
                node=bytes(params.node),
                authorized_voter=bytes(params.authorized_voter),
                authorized_withdrawer=bytes(params.authorized_withdrawer),
                commission=params.commission,
            ),
        )
    )
    return TransactionInstruction(
        keys=[
            AccountMeta(pubkey=params.vote, is_signer=False, is_writable=True),
            AccountMeta(pubkey=params.rent_sysvar or SYSVAR_RENT_PUBKEY, is_signer=False, is_writable=False),
            AccountMeta(pubkey=params.clock_sysvar or SYSVAR_CLOCK_PUBKEY, is_signer=False, is_writable=False),
            AccountMeta(pubkey=params.node, is_signer=True, is_writable=False),
        ],
        program_id=VOTE_PROGRAM_ID,
        data=data,
    )
