# standard imports
import json
import logging
from typing import Tuple

# third-party imports
import celery
from cic_types.models.person import Person, generate_metadata_pointer
from cic_types.models.person import generate_vcard_from_contact_data, manage_identity_data

# local imports
from cic_ussd.chain import Chain
from cic_ussd.db.models.user import User
from cic_ussd.error import UserMetadataNotFoundError
from cic_ussd.metadata import blockchain_address_to_metadata_pointer
from cic_ussd.operations import save_to_in_memory_ussd_session_data
from cic_ussd.redis import get_cached_data

logg = logging.getLogger(__file__)


def change_preferred_language_to_en(state_machine_data: Tuple[str, dict, User]):
    """This function changes the user's preferred language to english.
    :param state_machine_data: A tuple containing user input, a ussd session and user object.
    :type state_machine_data: tuple
    """
    user_input, ussd_session, user = state_machine_data
    user.preferred_language = 'en'
    User.session.add(user)
    User.session.commit()


def change_preferred_language_to_sw(state_machine_data: Tuple[str, dict, User]):
    """This function changes the user's preferred language to swahili.
    :param state_machine_data: A tuple containing user input, a ussd session and user object.
    :type state_machine_data: tuple
    """
    user_input, ussd_session, user = state_machine_data
    user.preferred_language = 'sw'
    User.session.add(user)
    User.session.commit()


def update_account_status_to_active(state_machine_data: Tuple[str, dict, User]):
    """This function sets user's account to active.
    :param state_machine_data: A tuple containing user input, a ussd session and user object.
    :type state_machine_data: tuple
    """
    user_input, ussd_session, user = state_machine_data
    user.activate_account()
    User.session.add(user)
    User.session.commit()


def process_gender_user_input(user: User, user_input: str):
    """
    :param user:
    :type user:
    :param user_input:
    :type user_input:
    :return:
    :rtype:
    """
    if user.preferred_language == 'en':
        if user_input == '1':
            gender = 'Male'
        else:
            gender = 'Female'
    else:
        if user_input == '1':
            gender = 'Mwanaume'
        else:
            gender = 'Mwanamke'
    return gender


def save_metadata_attribute_to_session_data(state_machine_data: Tuple[str, dict, User]):
    """This function saves first name data to the ussd session in the redis cache.
    :param state_machine_data: A tuple containing user input, a ussd session and user object.
    :type state_machine_data: tuple
    """
    user_input, ussd_session, user = state_machine_data

    # get current menu
    current_state = ussd_session.get('state')

    # define session data key from current state
    key = ''
    if 'given_name' in current_state:
        key = 'given_name'
    elif 'family_name' in current_state:
        key = 'family_name'
    elif 'gender' in current_state:
        key = 'gender'
        user_input = process_gender_user_input(user=user, user_input=user_input)
    elif 'location' in current_state:
        key = 'location'
    elif 'products' in current_state:
        key = 'products'

    # check if there is existing session data
    if ussd_session.get('session_data'):
        session_data = ussd_session.get('session_data')
        session_data[key] = user_input
    else:
        session_data = {
            key: user_input
        }
    save_to_in_memory_ussd_session_data(queue='cic-ussd', session_data=session_data, ussd_session=ussd_session)


def format_user_metadata(metadata: dict, user: User):
    """
    :param metadata:
    :type metadata:
    :param user:
    :type user:
    :return:
    :rtype:
    """
    gender = metadata.get('gender')
    given_name = metadata.get('given_name')
    family_name = metadata.get('family_name')
    location = {
        "area_name": metadata.get('location')
    }
    products = []
    if metadata.get('products'):
        products = metadata.get('products').split(',')
    phone_number = user.phone_number
    date_registered = int(user.created.replace().timestamp())
    blockchain_address = user.blockchain_address
    chain_spec = f'{Chain.spec.common_name()}:{Chain.spec.network_id()}'
    identities = manage_identity_data(
        blockchain_address=blockchain_address,
        blockchain_type=Chain.spec.engine(),
        chain_spec=chain_spec
    )
    return {
        "date_registered": date_registered,
        "gender": gender,
        "identities": identities,
        "location": location,
        "products": products,
        "vcard": generate_vcard_from_contact_data(
            family_name=family_name,
            given_name=given_name,
            tel=phone_number
        )
    }


def save_complete_user_metadata(state_machine_data: Tuple[str, dict, User]):
    """This function persists elements of the user metadata stored in session data
    :param state_machine_data: A tuple containing user input, a ussd session and user object.
    :type state_machine_data: tuple
    """
    user_input, ussd_session, user = state_machine_data

    # get session data
    metadata = ussd_session.get('session_data')

    # format metadata appropriately
    user_metadata = format_user_metadata(metadata=metadata, user=user)

    blockchain_address = user.blockchain_address
    s_create_user_metadata = celery.signature(
        'cic_ussd.tasks.metadata.create_user_metadata',
        [blockchain_address, user_metadata]
    )
    s_create_user_metadata.apply_async(queue='cic-ussd')


def edit_user_metadata_attribute(state_machine_data: Tuple[str, dict, User]):
    user_input, ussd_session, user = state_machine_data
    blockchain_address = user.blockchain_address
    key = generate_metadata_pointer(
        identifier=blockchain_address_to_metadata_pointer(blockchain_address=user.blockchain_address),
        cic_type='cic.person'
    )
    user_metadata = get_cached_data(key=key)

    if not user_metadata:
        raise UserMetadataNotFoundError(f'Expected user metadata but found none in cache for key: {blockchain_address}')

    given_name = ussd_session.get('session_data').get('given_name')
    family_name = ussd_session.get('session_data').get('family_name')
    gender = ussd_session.get('session_data').get('gender')
    location = ussd_session.get('session_data').get('location')
    products = ussd_session.get('session_data').get('products')

    # validate user metadata
    person = Person()
    user_metadata = json.loads(user_metadata)
    deserialized_person = person.deserialize(metadata=user_metadata)

    # edit specific metadata attribute
    if given_name:
        deserialized_person.given_name = given_name
    elif family_name:
        deserialized_person.family_name = family_name
    elif gender:
        deserialized_person.gender = gender
    elif location:
        # get existing location metadata:
        location_data = user_metadata.get('location')
        location_data['area_name'] = location
        deserialized_person.location = location_data
    elif products:
        deserialized_person.products = products

    edited_metadata = deserialized_person.serialize()

    s_edit_user_metadata = celery.signature(
        'cic_ussd.tasks.metadata.edit_user_metadata',
        [blockchain_address, edited_metadata, 'pgp']
    )
    s_edit_user_metadata.apply_async(queue='cic-ussd')


def get_user_metadata(state_machine_data: Tuple[str, dict, User]):
    user_input, ussd_session, user = state_machine_data
    blockchain_address = user.blockchain_address
    s_get_user_metadata = celery.signature(
        'cic_ussd.tasks.metadata.query_user_metadata',
        [blockchain_address]
    )
    s_get_user_metadata.apply_async(queue='cic-ussd')
