import datetime
import json
import sys
from pathlib import Path
from uuid import UUID

import pytest

import osw.model.entity as model
from osw.auth import CredentialManager
from osw.core import OSW, AddOverwriteClassOptions, OverwriteOptions
from osw.utils.wiki import get_full_title
from osw.wiki_tools import SearchParam
from osw.wtsite import WtSite

# run with: tox -e test -- --wiki_domain domain --wiki_username user --wiki_password pass


def test_store_and_load(wiki_domain, wiki_username, wiki_password):
    cm = CredentialManager()
    cm.add_credential(
        CredentialManager.UserPwdCredential(
            iri=wiki_domain, username=wiki_username, password=wiki_password
        )
    )
    wtsite = WtSite(WtSite.WtSiteConfig(iri=wiki_domain, cred_mngr=cm))
    osw = OSW(site=wtsite)
    # Check 1: Store an entity and download it again, delete afterward
    # Create an item with a label
    my_entity = model.Item(label=[model.Label(text="MyItem")])
    # Store the item in the OSW
    osw.store_entity(my_entity)
    # Load the item from the OSW
    my_entity2 = osw.load_entity("Item:" + OSW.get_osw_id(my_entity.uuid))
    # Check the stored item
    assert my_entity.label[0].text == my_entity2.label[0].text
    # Delete the item
    osw.delete_entity(my_entity)

    # Check 2: Store a more complex entity, create a local duplicate, with changed
    # properties, test the 'overwrite' param options, delete afterward
    # Make non-package scripts available for import
    cwd = Path(__file__).parent.absolute()
    tests_dir = cwd.parents[1] / "tests"
    sys.path.append(str(tests_dir))
    # Get required functions
    from tests.test_osl import (
        check_false,
        check_keep_existing,
        check_only_empty,
        check_replace_remote,
        check_true,
    )

    checks = [
        {  # Overwrite properties
            "overwrite": OverwriteOptions.true,
            "assert": check_true,
        },
        {  # Do not overwrite properties
            "overwrite": OverwriteOptions.false,
            "assert": check_false,
        },
        {  # Overwrite empty properties only
            "overwrite": OverwriteOptions.only_empty,
            "assert": check_only_empty,
        },
        {  # Replace the remote entity entirely
            "overwrite": AddOverwriteClassOptions.replace_remote,
            "assert": check_replace_remote,
        },
        {  # Keep the existing entity as is
            "overwrite": AddOverwriteClassOptions.keep_existing,
            "assert": check_keep_existing,
        },
    ]
    for check in checks:
        # Create a new item with some properties
        original_item = model.Item(
            label=[model.Label(text="My Item")],
            name="MyItem",
            iri="",  # Empty string property
            description=[],  # Empty list property
            query_label=None,  # Equal to a non-existing property
            image="File:OSWacacwdcawd.png",  # Property not in altered
        )
        print("Storing original entity...")
        osw.store_entity(original_item)
        # Alter some of the property values
        altered_props = {
            "label": [model.Label(text="My Item Duplicate")],
            "name": "MyItemDuplicate",
            "iri": "http://example.com/MyItemDuplicate",
            "description": [model.Label(text="This is a duplicate")],
            "query_label": "My Item Duplicate",
            "attachments": ["File:OSWacacwdcawd.pdf"],  # Property not in original
        }
        # Create a new item with the altered properties
        original_props = {
            key: value
            for key, value in original_item.dict().items()
            if key in ["uuid", "type"]
        }
        altered_item = model.Item(**{**original_props, **altered_props})
        # Update the item in the OSW
        print("Storing altered entity...")
        osw.store_entity(
            param=OSW.StoreEntityParam(
                entities=altered_item, overwrite=check["overwrite"]
            )
        )
        # Load the item from the OSW
        stored_item = osw.load_entity(
            entity_title=OSW.LoadEntityParam(
                titles="Item:" + OSW.get_osw_id(original_item.uuid), disable_cache=True
            )
        ).entities[0]
        # Check the stored item
        check["assert"](original_item, altered_item, stored_item)
        # Delete the item
        osw.delete_entity(original_item)


def test_query_instances(wiki_domain, wiki_username, wiki_password):
    """Store an entity, query instances of the category of the entity, make sure the
    new entity is contained in the list of returned instances, delete the entity."""
    cm = CredentialManager()
    cm.add_credential(
        CredentialManager.UserPwdCredential(
            iri=wiki_domain, username=wiki_username, password=wiki_password
        )
    )
    wtsite = WtSite(WtSite.WtSiteConfig(iri=wiki_domain, cred_mngr=cm))
    osw = OSW(site=wtsite)
    # Create an item with a label
    my_item = model.Item(label=[model.Label(text="MyItem")])
    fpt = get_full_title(my_item)
    # Store the item in the OSW
    osw.store_entity(my_item)
    # Query instances of the category of the entity
    instances = osw.query_instances(
        category=OSW.QueryInstancesParam(categories="Category:Item", limit=10000)
    )
    assert fpt in instances
    # Delete the item
    osw.delete_entity(my_item)


@pytest.mark.skip(reason="Temporarily disabled - Failing due to unresolved error")
def test_statement_creation(wiki_domain, wiki_username, wiki_password):
    cm = CredentialManager()
    cm.add_credential(
        CredentialManager.UserPwdCredential(
            iri=wiki_domain, username=wiki_username, password=wiki_password
        )
    )
    wtsite = WtSite(WtSite.WtSiteConfig(iri=wiki_domain, cred_mngr=cm))
    osw = OSW(site=wtsite)

    my_entity = model.Item(
        label=[model.Label(text="MyItem")],
        statements=[
            model.DataStatement(property="Property:TestProperty", value="TestValue")
        ],
    )

    osw.store_entity(my_entity)

    search_param = SearchParam(query="[[TestProperty::TestValue]]")
    full_page_titles = osw.site.semantic_search(search_param)
    assert f"Item:{OSW.get_osw_id(my_entity.uuid)}" in full_page_titles

    search_param = SearchParam(
        query="[[HasStatement.HasProperty::Property:TestProperty]]"
    )
    full_page_titles = osw.site.semantic_search(search_param)
    assert f"Item:{OSW.get_osw_id(my_entity.uuid)}" in full_page_titles

    osw.delete_entity(my_entity)


def test_characteristic_creation(wiki_domain, wiki_username, wiki_password):
    cm = CredentialManager()
    cm.add_credential(
        CredentialManager.UserPwdCredential(
            iri=wiki_domain, username=wiki_username, password=wiki_password
        )
    )
    wtsite = WtSite(WtSite.WtSiteConfig(iri=wiki_domain, cred_mngr=cm))
    osw = OSW(site=wtsite)

    osw.fetch_schema(
        OSW.FetchSchemaParam(
            schema_title=[
                "Category:OSWffe74f291d354037b318c422591c5023",  # CharacteristicType
                "Category:Property",
            ],
            mode="replace",
        )
    )

    # my_property = model.Property

    # Create a characteristic as instance of CharacteristicType
    my_characteristic = model.CharacteristicType(
        uuid="efad8086-4a76-47ca-b278-f5f944e5754b",
        name="TestCharacteristic",
        label=[model.Label(text="Test Characteristic")],
        description=[
            model.Description(text="A test characteristic for the osw python package")
        ],
        properties=[
            model.PrimitiveProperty(
                uuid="766e7171-a183-4f9c-a9af-28cfd27fb1d9",
                name="test_property",
                type="string",
                property_type="SimpleProperty",
            ),
            model.PrimitiveProperty(
                uuid="766e7171-a183-4f9c-a9af-28cfd27fb1d1",
                name="test_property2",
                rdf_property="Property:TestPropertyWithSchema",
                property_type="SimpleProperty",
            ),
        ],
    )

    # store it as instance, which generates the schema
    # note: namespace and meta_category should be detected automatically in the future
    osw.store_entity(
        OSW.StoreEntityParam(
            entities=[my_characteristic],
            namespace="Category",
            meta_category_title="Category:OSWffe74f291d354037b318c422591c5023",
            overwrite=OverwriteOptions.true,
        )
    )

    pages = osw.site.get_page(
        WtSite.GetPageParam(titles=["Property:TestPropertyWithSchema"])
    ).pages
    pp = pages[0]
    # schema: dict = pp.get_slot_content("jsonschema")
    new_schema = {"type": "number", "description": "Imported from property schema"}
    pp.set_slot_content("jsonschema", new_schema)
    pp.edit()

    # load the characteristic as category
    osw.fetch_schema(
        OSW.FetchSchemaParam(
            schema_title=[
                "Category:OSWefad80864a7647cab278f5f944e5754b"  # TestCharacteristic
            ],
            mode="append",
        )
    )

    # create an instance of the characteristic
    t = model.TestCharacteristic(test_property="Test", test_property2=1)
    assert t.test_property == "Test"
    assert t.test_property2 == 1

    # cleanup (disabled for paralle test matrix)
    # my_characteristic.meta = model.Meta(
    #     wiki_page=model.WikiPage(namespace="Category")
    # )  # namespace detection fails otherwise
    # osw.delete_entity(my_characteristic)
    # pp.delete()


def test_metaclass(wiki_domain, wiki_username, wiki_password):
    cm = CredentialManager()
    cm.add_credential(
        CredentialManager.UserPwdCredential(
            iri=wiki_domain, username=wiki_username, password=wiki_password
        )
    )
    wtsite = WtSite(WtSite.WtSiteConfig(iri=wiki_domain, cred_mngr=cm))
    osw = OSW(site=wtsite)

    osw.fetch_schema(
        OSW.FetchSchemaParam(
            schema_title=[
                "Category:OSWc11438cd6c814ed1a5a253555ee351b4",  # ProcessType
            ],
            mode="replace",
        )
    )

    # Create a characteristic as instance of ProcessType
    my_process = model.ProcessType(
        uuid="1a78f9b2-ae5f-4769-85ab-78070795939b",
        name="TestProcess",
        subclass_of=["Category:OSWe5aa96bffb1c4d95be7fbd46142ad203"],  # Process
        label=[model.Label(text="Test Process")],
        description=[
            model.Description(text="A test process for the osw python package")
        ],
        status_default="Item:OSWa2b4567ad4874ea1b9adfed19a3d06d1",  # InWork
        start_date_time_default=datetime.datetime.fromisoformat("2024-01-01T00:00:00"),
    )

    # store it as instance, which generates the schema
    # note: namespace and meta_category should be detected automatically in the future
    osw.store_entity(
        OSW.StoreEntityParam(
            entities=[my_process],
            namespace="Category",
            # meta_category_title=model.ProcessType.get_iri(),
            meta_category_title="Category:OSWc11438cd6c814ed1a5a253555ee351b4",
            overwrite=OverwriteOptions.true,
        )
    )

    # load the characteristic as category
    osw.fetch_schema(
        OSW.FetchSchemaParam(
            schema_title=["Category:" + my_process.get_osw_id()],
            mode="append",
        )
    )

    # create an instance of the characteristic
    t = model.TestProcess(label=[model.Label(text="Test Instance")])
    assert t.status == "Item:OSWa2b4567ad4874ea1b9adfed19a3d06d1"
    # not working yet - would require
    # start_date_time: Optional[datetime] = datetime.fromisoformat("2024-01-01T00:00:00")
    # assert t.start_date_time == datetime.datetime.fromisoformat("2024-01-01T00:00:00")


def oold_integration(wiki_domain, wiki_username, wiki_password):
    cm = CredentialManager()
    cm.add_credential(
        CredentialManager.UserPwdCredential(
            iri=wiki_domain, username=wiki_username, password=wiki_password
        )
    )
    wtsite = WtSite(WtSite.WtSiteConfig(iri=wiki_domain, cred_mngr=cm))
    # create OSW instance which auto-registers as a oold backend
    osw = OSW(site=wtsite)  # noqa: F841

    # create an instance of a keyword and store it
    k = model.Keyword(
        uuid=UUID("dcb75346-cf24-4793-bdb8-0c6fd68ebc2c"),
        label=[model.Label(text="Test Keyword", lang="en")],
        description=[model.Description(text="Test Description", lang="en")],
    )
    k.store_jsonld()

    # create an instance of an item linked to the keyword and store it
    # the keyword will be stored by its IRI ("Item:OSWdcb75346cf244793bdb80c6fd68ebc2c")
    i = model.Item(
        uuid=UUID("c65b130f-8aca-4d29-b1fd-6aef488759a6"),
        label=[model.Label(text="Test Item for OOLD Integration", lang="en")],
        keywords=[k],
    )
    assert i.label[0].text == "Test Item for OOLD Integration"
    jsonld_export = json.loads(i.json())
    assert jsonld_export["keywords"][0] == "Item:OSWdcb75346cf244793bdb80c6fd68ebc2c"
    i.store_jsonld()

    # load the item again by its IRI
    i_ = model.Item[i.get_iri()]
    assert i_.uuid == i.uuid

    # accessing the keyword property auto-resolves the keyword iri via the oold backend
    assert i_.keywords[0].label[0].text == "Test Keyword"
