import time
import json
import struct
import base64
import logging
from typing import Any
from datetime import datetime, timedelta
from typing import Optional
from contextlib import contextmanager

from pytime import pytime

from sirius_sdk.errors.indy_exceptions import IndyError
from sirius_sdk.errors.exceptions import StateMachineTerminatedWithError
from sirius_sdk.abstract.api import APICrypto


def utc_to_str(dt: datetime):
    return dt.strftime('%Y-%m-%dT%H:%M:%S') + '+0000'


def str_to_utc(s: str, raise_exceptions: bool = True) -> Optional[datetime]:
    tz_shift = None
    try:
        if '+' in s:
            s, shift = s.split('+')
            tz_shift = timedelta(hours=int(shift))
        s = s.replace('T', ' ')
        ret = pytime.parse(s)
        return ret + tz_shift
    except:
        logging.exception('Error while parse datetime')
        if raise_exceptions:
            raise
        else:
            return None


async def sign(crypto: APICrypto, value: Any, verkey: str, exclude_sig_data: bool = False) -> dict:
    timestamp_bytes = struct.pack(">Q", int(time.time()))

    sig_data_bytes = timestamp_bytes + json.dumps(value).encode('ascii')
    sig_data = base64.urlsafe_b64encode(sig_data_bytes).decode('ascii')

    signature_bytes = await crypto.crypto_sign(verkey, sig_data_bytes)
    signature = base64.urlsafe_b64encode(
        signature_bytes
    ).decode('ascii')

    data = {
        "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/signature/1.0/ed25519Sha512_single",
        "signer": verkey,
        "signature": signature
    }
    if not exclude_sig_data:
        data['sig_data'] = sig_data

    return data


async def verify_signed(crypto: APICrypto, signed: dict) -> (Any, bool):
    signature_bytes = base64.urlsafe_b64decode(signed['signature'].encode('ascii'))
    sig_data_bytes = base64.urlsafe_b64decode(signed['sig_data'].encode('ascii'))
    sig_verified = await crypto.crypto_verify(
        signed['signer'],
        sig_data_bytes,
        signature_bytes
    )
    data_bytes = base64.urlsafe_b64decode(signed['sig_data'])
    timestamp = struct.unpack(">Q", data_bytes[:8])
    field_json = data_bytes[8:]
    if isinstance(field_json, bytes):
        field_json = field_json.decode('utf-8')
    return json.loads(field_json), sig_verified


@contextmanager
def terminate_state_machine_on_indy_error(problem_code: str):
    try:
        yield
    except IndyError as e:
        term = StateMachineTerminatedWithError(
            problem_code=problem_code,
            explain=e.message
        )
        raise term
