# Copyright (c) 2022, 2026, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0,
# as published by the Free Software Foundation.
#
# This program is designed to work with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms, as
# designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an additional
# permission to link the program and your derivative works with the
# separately licensed software that they have either included with
# the program or referenced in the documentation.
#
# This program is distributed in the hope that it will be useful,  but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
# the GNU General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

import pytest
from mrs_plugin import lib
from ..helpers import (
    get_db_object_privileges,
    DbObjectCT,
    TableContents,
    get_default_db_object_init,
)


@pytest.mark.usefixtures("phone_book")
def test_add_db_object(phone_book, table_contents):
    db_object_table = table_contents("db_object")
    session = phone_book["session"]
    schema_id = phone_book["schema_id"]
    db_object_init = get_default_db_object_init(session, schema_id)

    with DbObjectCT(session, **db_object_init) as db_object_id:
        assert db_object_table.get("id", db_object_id) == {
            "auth_stored_procedure": None,
            "auto_detect_media_type": 1,
            "comments": "Object that will be removed",
            "crud_operations": ["CREATE", "READ", "UPDATE", "DELETE"],
            "db_schema_id": schema_id,
            "details": None,
            "enabled": 1,
            "format": "FEED",
            "id": db_object_id,
            "items_per_page": 10,
            "media_type": "application/json",
            "name": "ContactBasicInfo",
            "object_type": "VIEW",
            "options": {"aaa": "val aaa", "bbb": "val bbb"},
            "metadata": None,
            "request_path": "/view_contact_basic_info",
            "requires_auth": 0,
            "internal": 0,
        }

    assert db_object_table.same_as_snapshot


def test_update_db_object(phone_book, table_contents):
    db_object_table: TableContents = table_contents("db_object")

    with lib.core.MrsDbSession(session=phone_book["session"]) as session:
        db_object = get_default_db_object_init(session, phone_book["schema_id"])

        with DbObjectCT(session, **db_object) as db_object_id:
            expected = {
                "id": db_object_id,
                "auth_stored_procedure": db_object["auth_stored_procedure"],
                "auto_detect_media_type": int(db_object["auto_detect_media_type"]),
                "comments": db_object["comments"],
                "crud_operations": ["CREATE", "READ", "UPDATE", "DELETE"],
                "crud_operation_format": db_object["crud_operation_format"],
                "db_schema_id": phone_book["schema_id"],
                "enabled": int(db_object["enabled"]),
                "host_ctx": "/test",
                "items_per_page": db_object["items_per_page"],
                "media_type": db_object["media_type"],
                "name": db_object["db_object_name"],
                "object_type": db_object["db_object_type"],
                "options": db_object["options"],
                "metadata": None,
                "qualified_name": f'PhoneBook.{db_object["db_object_name"]}',
                "request_path": db_object["request_path"],
                "requires_auth": int(db_object["requires_auth"]),
                "schema_name": "PhoneBook",
                "schema_request_path": "/PhoneBook",
                "service_id": phone_book["service_id"],
                "internal": 0,
            }

            result = lib.db_objects.get_db_object(session, db_object_id)
            assert result is not None
            expected["changed_at"] = result["changed_at"]

            assert result == expected

            final = {
                "auth_stored_procedure": "<stored procedure text>",
                "auto_detect_media_type": True,
                "comments": "Comments updated",
                "crud_operation_format": "ITEM",
                "enabled": False,
                "items_per_page": 30,
                "media_type": "media type updated",
                "name": "ContactBasicInfoUpdated",
                "object_type": "TABLE",
                "options": {"bbb": "val bbb updated", "ccc": "val ccc inserted"},
            }

            for key, value in final.items():
                lib.db_objects.update_db_objects(session, [db_object_id], {key: value})
                expected[key] = int(value) if isinstance(value, bool) else value

                if key == "name":
                    expected["qualified_name"] = f"PhoneBook.{final[key]}"

                result = lib.db_objects.get_db_object(session, db_object_id)
                assert result is not None
                expected["changed_at"] = result["changed_at"]

                assert result == expected

            expected_object = {
                "comments": "Comment for object",
                "db_object_id": db_object_id,
                "id": db_object["objects"][0]["id"],
                "kind": "RESULT",
                "name": "MyServicePhoneBookContactsWithEmail",
                "position": 0,
                "row_ownership_field_id": None,
                "sdk_options": {"option1": "value 1", "option2": "value 2"},
                "options": {
                    "dataMappingViewDelete": True,
                    "dataMappingViewInsert": True,
                    "dataMappingViewUpdate": True,
                    "duality_view_delete": True,
                    "duality_view_insert": True,
                    "duality_view_update": True,
                },
            }
            objects = lib.db_objects.get_objects(session, db_object_id)
            assert objects == [expected_object]

            for object in objects:
                fields = lib.db_objects.get_object_fields_with_references(
                    session, object["id"]
                )

                expected_field = {
                    "allow_filtering": True,
                    "allow_sorting": False,
                    "caption": "- id",
                    "comments": None,
                    "db_column": {
                        "comment": "",
                        "datatype": "int",
                        "id_generation": None,
                        "is_generated": False,
                        "is_primary": db_object["objects"][0]["fields"][0]["db_column"][
                            "is_primary"
                        ],
                        "is_unique": False,
                        "name": "id",
                        "not_null": True,
                        "srid": None,
                    },
                    "enabled": True,
                    "id": db_object["objects"][0]["fields"][0]["id"],
                    "lev": 1,
                    "name": "id",
                    "no_check": False,
                    "no_update": False,
                    "object_id": db_object["objects"][0]["id"],
                    "object_reference": None,
                    "parent_reference_id": None,
                    "position": 1,
                    "represents_reference_id": None,
                    "sdk_options": None,
                    "options": fields[0]["options"],
                }

                assert fields == [expected_field]


def test_get_db_object(phone_book, mobile_phone_book):

    with lib.core.MrsDbSession(session=phone_book["session"]) as session:

        db_object = lib.db_objects.get_db_object(
            session=session, schema_id=phone_book["schema_id"]
        )
        assert db_object is None

        db_object = lib.db_objects.get_db_object(
            session=session,
            schema_id=phone_book["schema_id"],
            request_path="/test_abc",
            db_object_name="db1",
        )
        assert db_object is None

        db_object = lib.db_objects.get_db_object(
            session=session, db_object_id=phone_book["db_object_id"]
        )
        assert db_object is not None
        assert db_object["id"] == phone_book["db_object_id"]
        assert db_object["name"] == "Contacts"
        assert db_object["object_type"] == "TABLE"

        # This might take one of these values depending on if the db_object tests already ran
        assert db_object["request_path"] == "/Contacts"
        assert db_object["db_schema_id"] == phone_book["schema_id"]


def test_db_object_update(phone_book, table_contents):
    session = phone_book["session"]

    with lib.core.MrsDbSession(session=phone_book["session"]) as session:
        db_object_table: TableContents = table_contents("db_object")
        db_object_init = get_default_db_object_init(session, phone_book["schema_id"])

        with DbObjectCT(session, **db_object_init) as db_object_id:
            assert db_object_table.count == db_object_table.snapshot.count + 1
            db_object_table.take_snapshot()

            db_object = lib.db_objects.get_db_object(session, db_object_id)
            assert db_object is not None

            value = {
                "auth_stored_procedure": None,
                "auto_detect_media_type": 1,
                "comments": "Test table",
                "crud_operation_format": "ITEM",
                "crud_operations": ["CREATE", "READ"],
                "db_schema_id": phone_book["schema_id"],
                "enabled": 1,
                "items_per_page": 15,
                "media_type": None,
                "name": "ContactsWithEmail",
                "object_type": "TABLE",
                "options": None,
                "request_path": "/view_contects_wit_email2",
                "requires_auth": 0,
            }

            lib.db_objects.update_db_objects(
                session=session, db_object_ids=[db_object_id], value=value
            )
            assert not db_object_table.same_as_snapshot

            lib.db_objects.update_db_objects(
                session=session,
                db_object_ids=[db_object_id],
                value={
                    "auth_stored_procedure": db_object["auth_stored_procedure"],
                    "auto_detect_media_type": db_object["auto_detect_media_type"],
                    "comments": db_object["comments"],
                    "crud_operation_format": db_object["crud_operation_format"],
                    "crud_operations": db_object["crud_operations"],
                    "db_schema_id": db_object["db_schema_id"],
                    "enabled": db_object["enabled"],
                    "items_per_page": db_object["items_per_page"],
                    "media_type": db_object["media_type"],
                    "name": db_object["name"],
                    "object_type": db_object["object_type"],
                    "options": db_object["options"],
                    "metadata": None,
                    "request_path": db_object["request_path"],
                    "requires_auth": db_object["requires_auth"],
                },
            )
            assert db_object_table.same_as_snapshot

            result = lib.db_objects.get_db_object(session, db_object_id=db_object_id)
            assert result is not None

            grants = get_db_object_privileges(
                session, result["schema_name"], result["name"]
            )
            assert sorted(grants) == sorted(
                lib.db_objects.map_crud_operations(result["crud_operations"])
            )
