import unittest
import json

from typing import cast, Dict, Type, Any, Optional
from uuid import uuid4

import hypothesis

import hypothesis.strategies as st
from grapl_analyzerlib.node_types import PropType

from hypothesis import given

import pytest

from pydgraph import DgraphClient
from grapl_analyzerlib.grapl_client import MasterGraphClient, GraphClient
from grapl_analyzerlib.nodes.file import FileQuery, FileView
from grapl_analyzerlib.viewable import Viewable


def _upsert(client: DgraphClient, node_dict: Dict[str, Any]) -> str:
    node_dict["uid"] = "_:blank-0"
    node_key = node_dict["node_key"]
    query = f"""
        {{
            q0(func: eq(node_key, "{node_key}"), first: 1) {{
                    uid,
                    dgraph.type
                    expand(_all_)
            }}
        }}
        """
    txn = client.txn(read_only=False)

    try:
        res = json.loads(txn.query(query).json)["q0"]
        new_uid = None
        if res:
            node_dict["uid"] = res[0]["uid"]
            new_uid = res[0]["uid"]

        mutation = node_dict

        m_res = txn.mutate(set_obj=mutation, commit_now=True)
        uids = m_res.uids

        if new_uid is None:
            new_uid = uids["blank-0"]
        return str(new_uid)

    finally:
        txn.discard()


def upsert(
    client: DgraphClient,
    type_name: str,
    view_type: "Type[Viewable]",
    node_key: str,
    node_props: Dict[str, Any],
) -> "Viewable":
    node_props["node_key"] = node_key
    node_props["dgraph.type"] = list({type_name, "Base", "Entity"})
    uid = _upsert(client, node_props)
    node_props["uid"] = uid
    return view_type.from_dict(node_props, client)


def create_edge(
    client: DgraphClient, from_uid: str, edge_name: str, to_uid: str
) -> None:
    if edge_name[0] == "~":
        mut = {"uid": to_uid, edge_name[1:]: {"uid": from_uid}}

    else:
        mut = {"uid": from_uid, edge_name: {"uid": to_uid}}

    txn = client.txn(read_only=False)
    try:
        txn.mutate(set_obj=mut, commit_now=True)
    finally:
        txn.discard()


def get_or_create_file_node(
    local_client: DgraphClient,
    node_key,
    file_path: Optional[str] = None,
    file_extension: Optional[str] = None,
    file_mime_type: Optional[str] = None,
    file_version: Optional[str] = None,
    file_description: Optional[str] = None,
    file_product: Optional[str] = None,
    file_company: Optional[str] = None,
    file_directory: Optional[str] = None,
    file_hard_links: Optional[str] = None,
    signed: Optional[str] = None,
    signed_status: Optional[str] = None,
    md5_hash: Optional[str] = None,
    sha1_hash: Optional[str] = None,
    sha256_hash: Optional[str] = None,
    file_size: Optional[str] = None,
    file_inode: Optional[int] = None,
) -> FileView:
    file = {
        "node_key": node_key,
        "file_path": file_path,
        "file_extension": file_extension,
        "file_mime_type": file_mime_type,
        "file_size": file_size,
        "file_version": file_version,
        "file_description": file_description,
        "file_product": file_product,
        "file_company": file_company,
        "file_directory": file_directory,
        "file_inode": file_inode,
        "file_hard_links": file_hard_links,
        "signed": signed,
        "signed_status": signed_status,
        "md5_hash": md5_hash,
        "sha1_hash": sha1_hash,
        "sha256_hash": sha256_hash,
    }

    file = {k: v for (k, v) in file.items() if v is not None}

    return cast(FileView, upsert(local_client, "File", FileView, node_key, file))


file_gen = {
    "node_key": st.uuids(),
    "file_path": st.text(min_size=1),
    "file_extension": st.text(min_size=1),
    "file_mime_type": st.text(min_size=1),
    "file_size": st.integers(min_value=0, max_value=2 ** 48),
    "file_version": st.text(min_size=1),
    "file_description": st.text(min_size=1),
    "file_product": st.text(min_size=1),
    "file_company": st.text(min_size=1),
    "file_directory": st.text(min_size=1),
    "file_inode": st.integers(min_value=0, max_value=2 ** 48),
    "file_hard_links": st.text(min_size=1),
    "signed": st.booleans(),
    "signed_status": st.text(min_size=1),
    "md5_hash": st.text(min_size=1),
    "sha1_hash": st.text(min_size=1),
    "sha256_hash": st.text(min_size=1),
}


@pytest.mark.integration_test
class TestFileQuery(unittest.TestCase):
    #
    # @classmethod
    # def setUpClass(cls):
    #     local_client = DgraphClient(DgraphClientStub('localhost:9080'))
    #
    #     # drop_all(local_client)
    #     # time.sleep(3)
    #     # provision()
    #     # provision()

    # @classmethod
    # def tearDownClass(cls):
    #     local_client = DgraphClient(DgraphClientStub('localhost:9080'))
    #
    #     drop_all(local_client)
    #     provision()

    @hypothesis.settings(deadline=None)
    @given(**file_gen)
    def test_single_file_contains_key(
        self,
        node_key,
        file_path,
        file_extension,
        file_mime_type,
        file_size,
        file_version,
        file_description,
        file_product,
        file_company,
        file_directory,
        file_inode,
        file_hard_links,
        signed,
        signed_status,
        md5_hash,
        sha1_hash,
        sha256_hash,
    ):
        node_key = "test_single_file_contains_key" + str(node_key)
        signed = "true" if signed else "false"

        local_client = MasterGraphClient()

        get_or_create_file_node(
            local_client,
            node_key,
            file_path=file_path,
            file_extension=file_extension,
            file_mime_type=file_mime_type,
            file_size=file_size,
            file_version=file_version,
            file_description=file_description,
            file_product=file_product,
            file_company=file_company,
            file_directory=file_directory,
            file_inode=file_inode,
            file_hard_links=file_hard_links,
            signed=signed,
            signed_status=signed_status,
            md5_hash=md5_hash,
            sha1_hash=sha1_hash,
            sha256_hash=sha256_hash,
        )

        queried_proc = FileQuery().query_first(local_client, contains_node_key=node_key)

        assert node_key == queried_proc.node_key

        assert file_path == queried_proc.get_file_path() or ""
        assert file_extension == queried_proc.get_file_extension() or ""
        assert file_mime_type == queried_proc.get_file_mime_type() or ""
        assert file_version == queried_proc.get_file_version() or ""
        assert file_description == queried_proc.get_file_description() or ""
        assert file_product == queried_proc.get_file_product() or ""
        assert file_company == queried_proc.get_file_company() or ""
        assert file_directory == queried_proc.get_file_directory() or ""
        assert file_hard_links == queried_proc.get_file_hard_links() or ""
        assert signed == queried_proc.get_signed() or ""
        assert signed_status == queried_proc.get_signed_status() or ""
        assert md5_hash == queried_proc.get_md5_hash() or ""
        assert sha1_hash == queried_proc.get_sha1_hash() or ""
        assert sha256_hash == queried_proc.get_sha256_hash() or ""
        assert file_size == queried_proc.get_file_size()
        assert file_inode == queried_proc.get_file_inode()

    @hypothesis.settings(deadline=None)
    @given(**file_gen)
    def test_single_file_view_parity_eq(
        self,
        node_key,
        file_path,
        file_extension,
        file_mime_type,
        file_size,
        file_version,
        file_description,
        file_product,
        file_company,
        file_directory,
        file_inode,
        file_hard_links,
        signed,
        signed_status,
        md5_hash,
        sha1_hash,
        sha256_hash,
    ):
        node_key = "test_single_file_view_parity_eq" + str(node_key)
        signed = "true" if signed else "false"
        local_client = MasterGraphClient()

        get_or_create_file_node(
            local_client,
            node_key,
            file_path=file_path,
            file_extension=file_extension,
            file_mime_type=file_mime_type,
            file_size=file_size,
            file_version=file_version,
            file_description=file_description,
            file_product=file_product,
            file_company=file_company,
            file_directory=file_directory,
            file_inode=file_inode,
            file_hard_links=file_hard_links,
            signed=signed,
            signed_status=signed_status,
            md5_hash=md5_hash,
            sha1_hash=sha1_hash,
            sha256_hash=sha256_hash,
        )

        queried_file = (
            FileQuery()
            .with_node_key(eq=node_key)
            .with_file_path(eq=file_path)
            .with_file_extension(eq=file_extension)
            .with_file_mime_type(eq=file_mime_type)
            .with_file_size(eq=file_size)
            .with_file_version(eq=file_version)
            .with_file_description(eq=file_description)
            .with_file_product(eq=file_product)
            .with_file_company(eq=file_company)
            .with_file_directory(eq=file_directory)
            .with_file_inode(eq=file_inode)
            .with_file_hard_links(eq=file_hard_links)
            .with_signed(eq=signed)
            .with_signed_status(eq=signed_status)
            .with_md5_hash(eq=md5_hash)
            .with_sha1_hash(eq=sha1_hash)
            .with_sha256_hash(eq=sha256_hash)
            .query_first(local_client)
        )

        assert node_key == queried_file.node_key

        assert file_path == queried_file.get_file_path()
        assert file_extension == queried_file.get_file_extension()
        assert file_mime_type == queried_file.get_file_mime_type()
        assert file_size == queried_file.get_file_size()
        assert file_version == queried_file.get_file_version()
        assert file_description == queried_file.get_file_description()
        assert file_product == queried_file.get_file_product()
        assert file_company == queried_file.get_file_company()
        assert file_directory == queried_file.get_file_directory()
        assert file_inode == queried_file.get_file_inode()
        assert file_hard_links == queried_file.get_file_hard_links()
        assert signed == queried_file.get_signed()
        assert signed_status == queried_file.get_signed_status()
        assert md5_hash == queried_file.get_md5_hash()
        assert sha1_hash == queried_file.get_sha1_hash()
        assert sha256_hash == queried_file.get_sha256_hash()


if __name__ == "__main__":
    unittest.main()
