import os

import boto3
import pytest
import sure  # noqa # pylint: disable=unused-import
from botocore.exceptions import ClientError
from freezegun import freeze_time
from unittest import SkipTest

from moto import mock_managedblockchain
from . import helpers


@mock_managedblockchain
def test_vote_on_proposal_one_member_total_yes():
    conn = boto3.client("managedblockchain", region_name="us-east-1")

    # Create network
    response = conn.create_network(
        Name="testnetwork1",
        Framework="HYPERLEDGER_FABRIC",
        FrameworkVersion="1.2",
        FrameworkConfiguration=helpers.default_frameworkconfiguration,
        VotingPolicy=helpers.default_votingpolicy,
        MemberConfiguration=helpers.default_memberconfiguration,
    )
    network_id = response["NetworkId"]
    member_id = response["MemberId"]

    # Create proposal
    response = conn.create_proposal(
        NetworkId=network_id, MemberId=member_id, Actions=helpers.default_policy_actions
    )
    proposal_id = response["ProposalId"]

    # Get proposal details
    response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id)
    response["Proposal"]["NetworkId"].should.equal(network_id)
    response["Proposal"]["Status"].should.equal("IN_PROGRESS")

    # Vote yes
    response = conn.vote_on_proposal(
        NetworkId=network_id,
        ProposalId=proposal_id,
        VoterMemberId=member_id,
        Vote="YES",
    )

    # List proposal votes
    response = conn.list_proposal_votes(NetworkId=network_id, ProposalId=proposal_id)
    response["ProposalVotes"][0]["MemberId"].should.equal(member_id)

    # Get proposal details - should be APPROVED
    response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id)
    response["Proposal"]["Status"].should.equal("APPROVED")
    response["Proposal"]["YesVoteCount"].should.equal(1)
    response["Proposal"]["NoVoteCount"].should.equal(0)
    response["Proposal"]["OutstandingVoteCount"].should.equal(0)


@mock_managedblockchain
def test_vote_on_proposal_one_member_total_no():
    conn = boto3.client("managedblockchain", region_name="us-east-1")

    # Create network
    response = conn.create_network(
        Name="testnetwork1",
        Framework="HYPERLEDGER_FABRIC",
        FrameworkVersion="1.2",
        FrameworkConfiguration=helpers.default_frameworkconfiguration,
        VotingPolicy=helpers.default_votingpolicy,
        MemberConfiguration=helpers.default_memberconfiguration,
    )
    network_id = response["NetworkId"]
    member_id = response["MemberId"]

    # Create proposal
    response = conn.create_proposal(
        NetworkId=network_id, MemberId=member_id, Actions=helpers.default_policy_actions
    )
    proposal_id = response["ProposalId"]

    # Get proposal details
    response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id)
    response["Proposal"]["NetworkId"].should.equal(network_id)
    response["Proposal"]["Status"].should.equal("IN_PROGRESS")

    # Vote no
    response = conn.vote_on_proposal(
        NetworkId=network_id, ProposalId=proposal_id, VoterMemberId=member_id, Vote="NO"
    )

    # List proposal votes
    response = conn.list_proposal_votes(NetworkId=network_id, ProposalId=proposal_id)
    response["ProposalVotes"][0]["MemberId"].should.equal(member_id)

    # Get proposal details - should be REJECTED
    response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id)
    response["Proposal"]["Status"].should.equal("REJECTED")
    response["Proposal"]["YesVoteCount"].should.equal(0)
    response["Proposal"]["NoVoteCount"].should.equal(1)
    response["Proposal"]["OutstandingVoteCount"].should.equal(0)


@mock_managedblockchain
def test_vote_on_proposal_yes_greater_than():
    conn = boto3.client("managedblockchain", region_name="us-east-1")

    votingpolicy = {
        "ApprovalThresholdPolicy": {
            "ThresholdPercentage": 50,
            "ProposalDurationInHours": 24,
            "ThresholdComparator": "GREATER_THAN",
        }
    }

    # Create network
    response = conn.create_network(
        Name="testnetwork1",
        Framework="HYPERLEDGER_FABRIC",
        FrameworkVersion="1.2",
        FrameworkConfiguration=helpers.default_frameworkconfiguration,
        VotingPolicy=votingpolicy,
        MemberConfiguration=helpers.default_memberconfiguration,
    )
    network_id = response["NetworkId"]
    member_id = response["MemberId"]

    response = conn.create_proposal(
        NetworkId=network_id, MemberId=member_id, Actions=helpers.default_policy_actions
    )

    proposal_id = response["ProposalId"]

    # Vote yes
    response = conn.vote_on_proposal(
        NetworkId=network_id,
        ProposalId=proposal_id,
        VoterMemberId=member_id,
        Vote="YES",
    )

    # Get the invitation
    response = conn.list_invitations()
    invitation_id = response["Invitations"][0]["InvitationId"]

    # Create the member
    response = conn.create_member(
        InvitationId=invitation_id,
        NetworkId=network_id,
        MemberConfiguration=helpers.create_member_configuration(
            "testmember2", "admin", "Admin12345", False, "Test Member 2"
        ),
    )
    member_id2 = response["MemberId"]

    # Create another proposal
    response = conn.create_proposal(
        NetworkId=network_id, MemberId=member_id, Actions=helpers.default_policy_actions
    )

    proposal_id = response["ProposalId"]

    # Vote yes with member 1
    response = conn.vote_on_proposal(
        NetworkId=network_id,
        ProposalId=proposal_id,
        VoterMemberId=member_id,
        Vote="YES",
    )

    # Get proposal details
    response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id)
    response["Proposal"]["NetworkId"].should.equal(network_id)
    response["Proposal"]["Status"].should.equal("IN_PROGRESS")

    # Vote no with member 2
    response = conn.vote_on_proposal(
        NetworkId=network_id,
        ProposalId=proposal_id,
        VoterMemberId=member_id2,
        Vote="NO",
    )

    # Get proposal details
    response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id)
    response["Proposal"]["Status"].should.equal("REJECTED")


@mock_managedblockchain
def test_vote_on_proposal_no_greater_than():
    conn = boto3.client("managedblockchain", region_name="us-east-1")

    votingpolicy = {
        "ApprovalThresholdPolicy": {
            "ThresholdPercentage": 50,
            "ProposalDurationInHours": 24,
            "ThresholdComparator": "GREATER_THAN",
        }
    }

    # Create network
    response = conn.create_network(
        Name="testnetwork1",
        Framework="HYPERLEDGER_FABRIC",
        FrameworkVersion="1.2",
        FrameworkConfiguration=helpers.default_frameworkconfiguration,
        VotingPolicy=votingpolicy,
        MemberConfiguration=helpers.default_memberconfiguration,
    )
    network_id = response["NetworkId"]
    member_id = response["MemberId"]

    response = conn.create_proposal(
        NetworkId=network_id, MemberId=member_id, Actions=helpers.default_policy_actions
    )

    proposal_id = response["ProposalId"]

    # Vote yes
    response = conn.vote_on_proposal(
        NetworkId=network_id,
        ProposalId=proposal_id,
        VoterMemberId=member_id,
        Vote="YES",
    )

    # Get the invitation
    response = conn.list_invitations()
    invitation_id = response["Invitations"][0]["InvitationId"]

    # Create the member
    response = conn.create_member(
        InvitationId=invitation_id,
        NetworkId=network_id,
        MemberConfiguration=helpers.create_member_configuration(
            "testmember2", "admin", "Admin12345", False, "Test Member 2"
        ),
    )
    member_id2 = response["MemberId"]

    # Create another proposal
    response = conn.create_proposal(
        NetworkId=network_id, MemberId=member_id, Actions=helpers.default_policy_actions
    )

    proposal_id = response["ProposalId"]

    # Vote no with member 1
    response = conn.vote_on_proposal(
        NetworkId=network_id, ProposalId=proposal_id, VoterMemberId=member_id, Vote="NO"
    )

    # Vote no with member 2
    response = conn.vote_on_proposal(
        NetworkId=network_id,
        ProposalId=proposal_id,
        VoterMemberId=member_id2,
        Vote="NO",
    )

    # Get proposal details
    response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id)
    response["Proposal"]["NetworkId"].should.equal(network_id)
    response["Proposal"]["Status"].should.equal("REJECTED")


@mock_managedblockchain
def test_vote_on_proposal_expiredproposal():
    if os.environ.get("TEST_SERVER_MODE", "false").lower() == "true":
        raise SkipTest("Cant manipulate time in server mode")

    votingpolicy = {
        "ApprovalThresholdPolicy": {
            "ThresholdPercentage": 50,
            "ProposalDurationInHours": 1,
            "ThresholdComparator": "GREATER_THAN_OR_EQUAL_TO",
        }
    }

    conn = boto3.client("managedblockchain", region_name="us-east-1")

    with freeze_time("2015-01-01 12:00:00"):
        # Create network - need a good network
        response = conn.create_network(
            Name="testnetwork1",
            Framework="HYPERLEDGER_FABRIC",
            FrameworkVersion="1.2",
            FrameworkConfiguration=helpers.default_frameworkconfiguration,
            VotingPolicy=votingpolicy,
            MemberConfiguration=helpers.default_memberconfiguration,
        )
        network_id = response["NetworkId"]
        member_id = response["MemberId"]

        response = conn.create_proposal(
            NetworkId=network_id,
            MemberId=member_id,
            Actions=helpers.default_policy_actions,
        )

        proposal_id = response["ProposalId"]

    with freeze_time("2015-02-01 12:00:00"):
        # Vote yes - should set status to expired
        with pytest.raises(ClientError) as ex:
            conn.vote_on_proposal(
                NetworkId=network_id,
                ProposalId=proposal_id,
                VoterMemberId=member_id,
                Vote="YES",
            )
        err = ex.value.response["Error"]
        err["Code"].should.equal("InvalidRequestException")
        err["Message"].should.contain(
            "Proposal {0} is expired and you cannot vote on it.".format(proposal_id)
        )

        # Get proposal details - should be EXPIRED
        response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id)
        response["Proposal"]["Status"].should.equal("EXPIRED")


@mock_managedblockchain
def test_vote_on_proposal_status_check():
    conn = boto3.client("managedblockchain", region_name="us-east-1")

    # Create network
    response = conn.create_network(
        Name="testnetwork1",
        Framework="HYPERLEDGER_FABRIC",
        FrameworkVersion="1.2",
        FrameworkConfiguration=helpers.default_frameworkconfiguration,
        VotingPolicy=helpers.default_votingpolicy,
        MemberConfiguration=helpers.default_memberconfiguration,
    )
    network_id = response["NetworkId"]
    member_id = response["MemberId"]

    # Create 2 more members
    for counter in range(2, 4):
        response = conn.create_proposal(
            NetworkId=network_id,
            MemberId=member_id,
            Actions=helpers.default_policy_actions,
        )
        proposal_id = response["ProposalId"]

        # Vote yes
        response = conn.vote_on_proposal(
            NetworkId=network_id,
            ProposalId=proposal_id,
            VoterMemberId=member_id,
            Vote="YES",
        )

    memberidlist = [None, None, None]
    memberidlist[0] = member_id
    for counter in range(2, 4):
        # Get the invitation
        response = conn.list_invitations()
        invitation_id = helpers.select_invitation_id_for_network(
            response["Invitations"], network_id, "PENDING"
        )[0]

        # Create the member
        response = conn.create_member(
            InvitationId=invitation_id,
            NetworkId=network_id,
            MemberConfiguration=helpers.create_member_configuration(
                "testmember" + str(counter),
                "admin",
                "Admin12345",
                False,
                "Test Member " + str(counter),
            ),
        )
        member_id = response["MemberId"]
        memberidlist[counter - 1] = member_id

    # Should be no more pending invitations
    response = conn.list_invitations()
    pendinginvs = helpers.select_invitation_id_for_network(
        response["Invitations"], network_id, "PENDING"
    )
    pendinginvs.should.have.length_of(0)

    # Create another proposal
    response = conn.create_proposal(
        NetworkId=network_id, MemberId=member_id, Actions=helpers.default_policy_actions
    )

    proposal_id = response["ProposalId"]

    # Vote yes with member 1
    response = conn.vote_on_proposal(
        NetworkId=network_id,
        ProposalId=proposal_id,
        VoterMemberId=memberidlist[0],
        Vote="YES",
    )

    # Vote yes with member 2
    response = conn.vote_on_proposal(
        NetworkId=network_id,
        ProposalId=proposal_id,
        VoterMemberId=memberidlist[1],
        Vote="YES",
    )

    # Get proposal details - now approved (2 yes, 1 outstanding)
    response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id)
    response["Proposal"]["NetworkId"].should.equal(network_id)
    response["Proposal"]["Status"].should.equal("APPROVED")

    # Should be one pending invitation
    response = conn.list_invitations()
    pendinginvs = helpers.select_invitation_id_for_network(
        response["Invitations"], network_id, "PENDING"
    )
    pendinginvs.should.have.length_of(1)

    # Vote with member 3 - should throw an exception and not create a new invitation
    with pytest.raises(ClientError) as ex:
        conn.vote_on_proposal(
            NetworkId=network_id,
            ProposalId=proposal_id,
            VoterMemberId=memberidlist[2],
            Vote="YES",
        )
    err = ex.value.response["Error"]
    err["Code"].should.equal("InvalidRequestException")
    err["Message"].should.contain("and you cannot vote on it")

    # Should still be one pending invitation
    response = conn.list_invitations()
    pendinginvs = helpers.select_invitation_id_for_network(
        response["Invitations"], network_id, "PENDING"
    )
    pendinginvs.should.have.length_of(1)


@mock_managedblockchain
def test_vote_on_proposal_badnetwork():
    conn = boto3.client("managedblockchain", region_name="us-east-1")

    with pytest.raises(ClientError) as ex:
        conn.vote_on_proposal(
            NetworkId="n-ABCDEFGHIJKLMNOP0123456789",
            ProposalId="p-ABCDEFGHIJKLMNOP0123456789",
            VoterMemberId="m-ABCDEFGHIJKLMNOP0123456789",
            Vote="YES",
        )
    err = ex.value.response["Error"]
    err["Code"].should.equal("ResourceNotFoundException")
    err["Message"].should.contain("Network n-ABCDEFGHIJKLMNOP0123456789 not found")


@mock_managedblockchain
def test_vote_on_proposal_badproposal():
    conn = boto3.client("managedblockchain", region_name="us-east-1")

    # Create network - need a good network
    response = conn.create_network(
        Name="testnetwork1",
        Framework="HYPERLEDGER_FABRIC",
        FrameworkVersion="1.2",
        FrameworkConfiguration=helpers.default_frameworkconfiguration,
        VotingPolicy=helpers.default_votingpolicy,
        MemberConfiguration=helpers.default_memberconfiguration,
    )
    network_id = response["NetworkId"]

    with pytest.raises(ClientError) as ex:
        conn.vote_on_proposal(
            NetworkId=network_id,
            ProposalId="p-ABCDEFGHIJKLMNOP0123456789",
            VoterMemberId="m-ABCDEFGHIJKLMNOP0123456789",
            Vote="YES",
        )
    err = ex.value.response["Error"]
    err["Code"].should.equal("ResourceNotFoundException")
    err["Message"].should.contain("Proposal p-ABCDEFGHIJKLMNOP0123456789 not found")


@mock_managedblockchain
def test_vote_on_proposal_badmember():
    conn = boto3.client("managedblockchain", region_name="us-east-1")

    # Create network - need a good network
    response = conn.create_network(
        Name="testnetwork1",
        Framework="HYPERLEDGER_FABRIC",
        FrameworkVersion="1.2",
        FrameworkConfiguration=helpers.default_frameworkconfiguration,
        VotingPolicy=helpers.default_votingpolicy,
        MemberConfiguration=helpers.default_memberconfiguration,
    )
    network_id = response["NetworkId"]
    member_id = response["MemberId"]

    response = conn.create_proposal(
        NetworkId=network_id, MemberId=member_id, Actions=helpers.default_policy_actions
    )

    proposal_id = response["ProposalId"]

    with pytest.raises(ClientError) as ex:
        conn.vote_on_proposal(
            NetworkId=network_id,
            ProposalId=proposal_id,
            VoterMemberId="m-ABCDEFGHIJKLMNOP0123456789",
            Vote="YES",
        )
    err = ex.value.response["Error"]
    err["Code"].should.equal("ResourceNotFoundException")
    err["Message"].should.contain("Member m-ABCDEFGHIJKLMNOP0123456789 not found")


@mock_managedblockchain
def test_vote_on_proposal_badvote():
    conn = boto3.client("managedblockchain", region_name="us-east-1")

    # Create network - need a good network
    response = conn.create_network(
        Name="testnetwork1",
        Framework="HYPERLEDGER_FABRIC",
        FrameworkVersion="1.2",
        FrameworkConfiguration=helpers.default_frameworkconfiguration,
        VotingPolicy=helpers.default_votingpolicy,
        MemberConfiguration=helpers.default_memberconfiguration,
    )
    network_id = response["NetworkId"]
    member_id = response["MemberId"]

    response = conn.create_proposal(
        NetworkId=network_id, MemberId=member_id, Actions=helpers.default_policy_actions
    )

    proposal_id = response["ProposalId"]

    with pytest.raises(ClientError) as ex:
        conn.vote_on_proposal(
            NetworkId=network_id,
            ProposalId=proposal_id,
            VoterMemberId=member_id,
            Vote="FOO",
        )
    err = ex.value.response["Error"]
    err["Code"].should.equal("BadRequestException")
    err["Message"].should.contain("Invalid request body")


@mock_managedblockchain
def test_vote_on_proposal_alreadyvoted():
    conn = boto3.client("managedblockchain", region_name="us-east-1")

    votingpolicy = {
        "ApprovalThresholdPolicy": {
            "ThresholdPercentage": 50,
            "ProposalDurationInHours": 24,
            "ThresholdComparator": "GREATER_THAN",
        }
    }

    # Create network - need a good network
    response = conn.create_network(
        Name="testnetwork1",
        Framework="HYPERLEDGER_FABRIC",
        FrameworkVersion="1.2",
        FrameworkConfiguration=helpers.default_frameworkconfiguration,
        VotingPolicy=votingpolicy,
        MemberConfiguration=helpers.default_memberconfiguration,
    )
    network_id = response["NetworkId"]
    member_id = response["MemberId"]

    response = conn.create_proposal(
        NetworkId=network_id, MemberId=member_id, Actions=helpers.default_policy_actions
    )

    proposal_id = response["ProposalId"]

    # Vote yes
    response = conn.vote_on_proposal(
        NetworkId=network_id,
        ProposalId=proposal_id,
        VoterMemberId=member_id,
        Vote="YES",
    )

    # Get the invitation
    response = conn.list_invitations()
    invitation_id = response["Invitations"][0]["InvitationId"]

    # Create the member
    response = conn.create_member(
        InvitationId=invitation_id,
        NetworkId=network_id,
        MemberConfiguration=helpers.create_member_configuration(
            "testmember2", "admin", "Admin12345", False, "Test Member 2"
        ),
    )

    # Create another proposal
    response = conn.create_proposal(
        NetworkId=network_id, MemberId=member_id, Actions=helpers.default_policy_actions
    )

    proposal_id = response["ProposalId"]

    # Get proposal details
    response = conn.get_proposal(NetworkId=network_id, ProposalId=proposal_id)
    response["Proposal"]["NetworkId"].should.equal(network_id)
    response["Proposal"]["Status"].should.equal("IN_PROGRESS")

    # Vote yes with member 1
    response = conn.vote_on_proposal(
        NetworkId=network_id,
        ProposalId=proposal_id,
        VoterMemberId=member_id,
        Vote="YES",
    )

    # Vote yes with member 1 again
    with pytest.raises(ClientError) as ex:
        conn.vote_on_proposal(
            NetworkId=network_id,
            ProposalId=proposal_id,
            VoterMemberId=member_id,
            Vote="YES",
        )
    err = ex.value.response["Error"]
    err["Code"].should.equal("ResourceAlreadyExistsException")
    err["Message"].should.contain(
        "Member {0} has already voted on proposal {1}.".format(member_id, proposal_id)
    )


@mock_managedblockchain
def test_list_proposal_votes_badnetwork():
    conn = boto3.client("managedblockchain", region_name="us-east-1")

    with pytest.raises(ClientError) as ex:
        conn.list_proposal_votes(
            NetworkId="n-ABCDEFGHIJKLMNOP0123456789",
            ProposalId="p-ABCDEFGHIJKLMNOP0123456789",
        )
    err = ex.value.response["Error"]
    err["Code"].should.equal("ResourceNotFoundException")
    err["Message"].should.contain("Network n-ABCDEFGHIJKLMNOP0123456789 not found")


@mock_managedblockchain
def test_list_proposal_votes_badproposal():
    conn = boto3.client("managedblockchain", region_name="us-east-1")

    # Create network
    response = conn.create_network(
        Name="testnetwork1",
        Framework="HYPERLEDGER_FABRIC",
        FrameworkVersion="1.2",
        FrameworkConfiguration=helpers.default_frameworkconfiguration,
        VotingPolicy=helpers.default_votingpolicy,
        MemberConfiguration=helpers.default_memberconfiguration,
    )
    network_id = response["NetworkId"]

    with pytest.raises(ClientError) as ex:
        conn.list_proposal_votes(
            NetworkId=network_id, ProposalId="p-ABCDEFGHIJKLMNOP0123456789"
        )
    err = ex.value.response["Error"]
    err["Code"].should.equal("ResourceNotFoundException")
    err["Message"].should.contain("Proposal p-ABCDEFGHIJKLMNOP0123456789 not found")