import pytest

from .. import graph
from ..common import LoadContext, RecordField
from ..db import Database


def create_record_def(record_type):
    return f"""
recordtype({record_type}) {{
    field(INP, DBF_INLINK) {{
    }}
    field(OUT, DBF_OUTLINK) {{
    }}
    field(VAL, DBF_STRING) {{
    }}
}}
"""


def create_record(record_type, record_name, fields, filename=None):
    filename = filename or f"{record_name}.db"
    field_source = "\n".join(
        f'field({field_name}, "{field_value}")'
        for field_name, field_value in fields.items()
    )
    db_source = create_record_def(record_type) + f"""
record({record_type}, "{record_name}") {{
    {field_source}
}}
"""
    print(f"---- {filename}:")
    for lineno, line in enumerate(db_source.splitlines(), 1):
        print(lineno, line)

    db = Database.from_string(db_source, filename=filename)
    return db.records[record_name]


def test_simple_graph(dbd: Database):
    database = {
        "record_a": create_record("ai", "record_a", {"INP": "record_b CPP MS", "VAL": "10"}),
        "record_b": create_record("ao", "record_b", {"OUT": "record_c CA", "VAL": "20"}),
    }
    relations = graph.build_database_relations(
        database,
        record_types=dbd.record_types,
    )
    print(database["record_a"])
    assert relations["record_a"]["record_b"] == [
        (
            database["record_a"].fields["INP"],
            database["record_b"].fields["VAL"],
            ["CPP", "MS"],
        ),
    ]
    assert relations["record_b"]["record_a"] == [
        (
            database["record_b"].fields["VAL"],
            database["record_a"].fields["INP"],
            ["CPP", "MS"],
        ),
    ]
    assert relations["record_b"]["record_c"] == [
        (
            database["record_b"].fields["OUT"],
            RecordField(
                dtype='unknown', name='VAL', value='(unknown-record)',
                context=(LoadContext("unknown", 0), ),
            ),
            ["CA"],
        ),
    ]


@pytest.fixture
def dbd():
    return Database.from_string(
        "\n".join((
            create_record_def(rec_type)
            for rec_type in ["ai", "ao"]
        )),
        filename="the.dbd",
    )


def test_combine_relations(dbd: Database):
    database_1 = {
        "record_a": create_record("ai", "record_a", {"INP": "record_b CPP MS", "VAL": "10"}),
        "record_b": create_record("ao", "record_b", {"OUT": "record_c CA", "VAL": "20"}),
    }
    database_2 = {
        "record_c": create_record("ao", "record_c", {"OUT": "record_d.INP", "VAL": "10"}),
        "record_d": create_record("ai", "record_d", {"INP": "", "VAL": "10"}),
        "record_e": create_record("ai", "record_e", {"INP": "record_a CP", "VAL": "10"}),
    }
    relations = graph.build_database_relations(
        database_1,
        record_types=dbd.record_types,
    )
    graph.combine_relations(
        relations,
        database_1,
        graph.build_database_relations(database_2),
        database_2,
        record_types=dbd.record_types,
    )

    assert relations["record_a"]["record_b"] == [
        (
            database_1["record_a"].fields["INP"],
            database_1["record_b"].fields["VAL"],
            ["CPP", "MS"],
        ),
    ]
    assert relations["record_b"]["record_a"] == [
        (
            database_1["record_b"].fields["VAL"],
            database_1["record_a"].fields["INP"],
            ["CPP", "MS"],
        ),
    ]
    assert relations["record_b"]["record_c"] == [
        (
            database_1["record_b"].fields["OUT"],
            # RecordField(
            #     dtype='unknown', name='VAL', value='(unknown-record)',
            #     context=(LoadContext("unknown", 0), ),
            # ),
            database_2["record_c"].fields["VAL"],
            ["CA"],
        ),
    ]
    assert relations["record_c"]["record_d"] == [
        (
            database_2["record_c"].fields["OUT"],
            database_2["record_d"].fields["INP"],
            [],
        ),
    ]
    assert relations["record_d"]["record_c"] == [
        (
            database_2["record_d"].fields["INP"],
            database_2["record_c"].fields["OUT"],
            [],
        ),
    ]
    assert relations["record_e"]["record_a"] == [
        (
            database_2["record_e"].fields["INP"],
            database_1["record_a"].fields["VAL"],
            ["CP"],
        ),
    ]


def test_with_unset_field(dbd: Database):
    database_1 = {
        "record_a": create_record("ai", "record_a", {"INP": "record_b CPP MS", "VAL": "10"}),
        "record_b": create_record("ao", "record_b", {"OUT": "record_c CA"}),
    }
    relations = graph.build_database_relations(database_1, record_types=dbd.record_types)
    assert relations["record_a"]["record_b"] == [
        (
            database_1["record_a"].fields["INP"],
            # database_1["record_b"].fields["VAL"],  # <-- not defined, but in dbd
            RecordField(
                dtype="DBF_STRING",
                name="VAL",
                value="",
                context=(LoadContext("the.dbd", 17),),
            ),
            ["CPP", "MS"],
        ),
    ]


def test_with_invalid_field(dbd: Database):
    database_1 = {
        "record_a": create_record("ai", "record_a", {"INP": "record_b.INVAL CPP MS", "VAL": "10"}),
        "record_b": create_record("ao", "record_b", {"OUT": "record_c CA"}),
    }
    relations = graph.build_database_relations(database_1, record_types=dbd.record_types)
    assert relations["record_a"]["record_b"] == [
        (
            database_1["record_a"].fields["INP"],
            RecordField(
                dtype="invalid",
                name="INVAL",
                value="(invalid-field)",
                context=(LoadContext("unknown", 0),),
            ),
            ["CPP", "MS"],
        ),
    ]


def test_with_weird_record_type(dbd: Database):
    database_1 = {
        "record_a": create_record("ai", "record_a", {"INP": "record_b.ABC CPP MS", "VAL": "10"}),
        "record_b": create_record("aq", "record_b", {"OUT": "record_c CA"}),
    }
    relations = graph.build_database_relations(database_1, record_types=dbd.record_types)
    assert relations["record_a"]["record_b"] == [
        (
            database_1["record_a"].fields["INP"],
            RecordField(
                dtype="invalid",
                name="ABC",
                value="(invalid-record-type)",
                context=(LoadContext("unknown", 0),),
            ),
            ["CPP", "MS"],
        ),
    ]


def test_with_alias(dbd: Database):
    database_1 = {
        "record_a": create_record("ai", "record_a", {"INP": "foobar CPP MS", "VAL": "10"}),
        "record_b": create_record("ao", "record_b", {"VAL": "5"}),
    }
    relations = graph.build_database_relations(
        database_1, record_types=dbd.record_types,
        aliases={"foobar": "record_b"},
    )
    assert relations["record_a"]["record_b"] == [
        (
            database_1["record_a"].fields["INP"],
            database_1["record_b"].fields["VAL"],
            ["CPP", "MS"],
        ),
    ]


def test_combine_with_alias(dbd: Database):
    database_1 = {
        "record_a": create_record("ai", "record_a", {"INP": "record_b CPP MS", "VAL": "10"}),
        "record_b": create_record("ao", "record_b", {"OUT": "alias_c CA", "VAL": "20"}),
    }
    database_2 = {
        "record_c": create_record("ao", "record_c", {"OUT": "alias_d.INP", "VAL": "10"}),
        # Refer to record_a through
        "record_d": create_record("ai", "record_d", {"INP": "record_a", "VAL": "10"}),
        # ... but _also_ refer to it as alias_a; when combining the relations this makes
        # it more fun
        "record_e": create_record("ai", "record_e", {"INP": "alias_a CP", "VAL": "10"}),
    }
    relations = graph.build_database_relations(
        database_1,
        aliases={
            "alias_a": "record_a",
            "alias_b": "record_b",
        },
        record_types=dbd.record_types,
    )

    relations_2 = graph.build_database_relations(
        database_2,
        aliases={
            "alias_c": "record_c",
            "alias_d": "record_d",
            "alias_e": "record_e",
        },
        record_types=dbd.record_types,
    )

    graph.combine_relations(
        relations,
        database_1,
        relations_2,
        database_2,
        aliases={
            "alias_a": "record_a",
            "alias_b": "record_b",
            "alias_c": "record_c",
            "alias_d": "record_d",
            "alias_e": "record_e",
        },
        record_types=dbd.record_types,
    )

    assert relations["record_a"]["record_b"] == [
        (
            database_1["record_a"].fields["INP"],
            database_1["record_b"].fields["VAL"],
            ["CPP", "MS"],
        ),
    ]
    assert relations["record_b"]["record_a"] == [
        (
            database_1["record_b"].fields["VAL"],
            database_1["record_a"].fields["INP"],
            ["CPP", "MS"],
        ),
    ]
    assert relations["record_b"]["record_c"] == [
        (
            database_1["record_b"].fields["OUT"],
            # RecordField(
            #     dtype='unknown', name='VAL', value='(unknown-record)',
            #     context=(LoadContext("unknown", 0), ),
            # ),
            database_2["record_c"].fields["VAL"],
            ["CA"],
        ),
    ]
    assert relations["record_c"]["record_d"] == [
        (
            database_2["record_c"].fields["OUT"],
            database_2["record_d"].fields["INP"],
            [],
        ),
    ]
    assert relations["record_d"]["record_c"] == [
        (
            database_2["record_d"].fields["INP"],
            database_2["record_c"].fields["OUT"],
            [],
        ),
    ]
    assert relations["record_d"]["record_a"] == [
        (
            database_2["record_d"].fields["INP"],
            database_1["record_a"].fields["VAL"],
            [],
        ),
    ]
    assert relations["record_e"]["record_a"] == [
        (
            database_2["record_e"].fields["INP"],
            database_1["record_a"].fields["VAL"],
            ["CP"],
        ),
    ]
    assert relations["record_a"]["record_e"] == [
        (
            database_1["record_a"].fields["VAL"],
            database_2["record_e"].fields["INP"],
            ["CP"],
        ),
    ]


def test_list_value_graph(dbd: Database):
    database_1 = {
        "record_a": create_record(
            "ai",
            "record_a",
            {
                # Note: field(INP, ["this is a long string array value"])
                "INP": ("this is a long string array value",),
                "VAL": "10",
            },
        ),
    }

    # Ensure this doesn't raise
    graph.build_database_relations(
        database_1,
        record_types=dbd.record_types,
    )
