import os
import logging
import random
import alembic
import argparse
import datetime
import hashlib
import math

import confini

from cic_eth.db import SessionBase
from cic_eth.db import dsn_from_config
from cic_eth.db.enum import StatusEnum
from cic_eth.db.models.tx import Tx
from cic_eth.db.models.tx import TxCache
from cic_eth.db.models.otx import Otx
from cic_eth.db.models.otx import Otx


# connect to database
dsn = dsn_from_config(config)
SessionBase.connect(dsn)

pending_states = [
    StatusEnum.PENDING,
    StatusEnum.SENDFAIL,
    StatusEnum.WAITFORGAS,
        ]

synced_states = [
    StatusEnum.SENT,
    StatusEnum.CANCELLED,
        ]

failed_states = [
    StatusEnum.REJECTED,
    StatusEnum.FUBAR,
        ]

mined_states = [
    StatusEnum.REVERTED,
    StatusEnum.SUCCESS,
        ]

all_states = pending_states + synced_states + failed_states + mined_states
   

class MalarkeyCommitter():
    """Adds entries to the cic-eth transaction cache backend.

    All values generated by this class are deterministic but completely arbitrary.

    :param generator: Generator instance used to generate the transactions
    :type generator: Generator
    :param block_change_factor: Probabiliy of advancing to next block per entry (Optional; default: 0.6)
    :type block_change_factor: float
    :param tx_index_max: Maximum transaction index number to generate (Optional; default: 200)
    :type ix_index_max: int 
    :param block_time_seconds: Seconds to advance block timestamp datetime on block advance
    :type block_time_seconds: int
    """
    def __init__(self, generator, block_change_factor=0.6, tx_index_max=200, block_time_seconds=15):
        self.g = generator
        self.s = SessionBase.create_session()
        self.datetime = datetime.datetime.utcnow()
        self.block = 0
        self.tx = 0
        self.block_change_factor = block_change_factor
        self.tx_index_max = tx_index_max
        self.block_time_seconds = block_time_seconds


    def advance(self):
        """Randomly advances block number and transaction index.
        """
        if random.random() < self.block_change_factor:
            self.block_advance()
        tx_advance = random.randint(0, self.tx_index_max - self.tx) 
        self.tx_advance(tx_advance)


    def tx_advance(self, n=1):
        """Advance tx index number.

        :param n: Will be added to tx index (Optional; default: 1)
        :type n: int
        """
        self.tx += n


    def block_advance(self, n=1):
        """Advance block number.

        :param n: Will be added to tx index (Optional; default: 1)
        :type n: int
        """
        self.block += n
        self.datetime += datetime.timedelta(seconds=self.block_time_seconds)
        self.tx = 0


    def stamp(self, entry):
        """Calls the random advance method, and sets the corresponding block, tx index and dates on the entry.
        """
        self.advance()
        entry.mine(self.block, self.tx, self.datetime)


    def commit_func(self, entry):
        """Implements interface needed for GeneratorSession.commit_func, storing a given entry.

        Generates random block and tx index for entry, advancing date correspondingly.

        Also generates random transaction queue states for the entries:

        - For any entry with foreign origin, queue state will always be in the set of "mined" states if applicable.
        - For any entry with local origin and "mined" queue state, an incoming transaction record will be added.
        
        :param entry: Entry to commit
        :type entry: GeneratorEntry
        """
        self.stamp(entry)
        nonce = self.g.get_nonce(entry.sender)
        tx_hash = '0x{:064x}'.format(random.getrandbits(256))
        signed_tx_bytes = b''
        h = hashlib.sha256()
        for i in range(4):
            h.update(tx_hash.encode('utf-8'))
            signed_tx_bytes += h.digest()
        signed_tx = '0x' + signed_tx_bytes.hex()

        state = None
        if entry.sender in self.g.addresses['foreign']:
            state = random.choice(mined_states)
        else:
            state = random.choice(all_states)

        otx = None
        tx = None

        if entry.sender in self.g.addresses['local']:
            otx = Otx(
                nonce,
                entry.sender,
                tx_hash,
                signed_tx,
                    )
            otx.status = state
            self.s.add(otx)
            self.s.flush()

        if state in mined_states:
            tx = Tx(
                tx_hash,
                state == StatusEnum.SUCCESS,
                    )
            self.s.add(tx)
            self.s.flush()

        self.s.commit()

        txc = TxCache(
            tx_hash,
            entry.sender,
            entry.recipient,
            entry.source_token,
            entry.destination_token,
            entry.from_value,
            entry.to_value,
            entry.block,
            entry.tx,
            entry.sender in self.g.addresses['local'],
                )
        txc.date_registered = entry.datetime
        self.s.add(txc)
        self.s.commit()

        logg.info('committed {}'.format(entry))