# Copyright (c) 2021, 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
import os
from pathlib import Path
from ...schemas import *
import mysqlsh
from .helpers import SchemaCT, DbObjectCT, get_default_db_object_init

schema_create_statement = """CREATE OR REPLACE REST SCHEMA /PhoneBook ON SERVICE /test
    FROM `PhoneBook`
    AUTHENTICATION NOT REQUIRED;

CREATE OR REPLACE REST VIEW /Contacts
    ON SERVICE /test SCHEMA /PhoneBook
    AS `PhoneBook`.`Contacts` CLASS MyServiceAnalogPhoneBookContacts {
        id: id @KEY @SORTABLE,
        fName: f_name,
        lName: l_name,
        number: number,
        email: email
    }
    AUTHENTICATION REQUIRED;"""


def test_add_schema(phone_book, table_contents):
    session = phone_book["session"]
    schemas_table = table_contents("db_schema")

    with SchemaCT(
        session, phone_book["service_id"], "PhoneBook", "/PhoneBook2"
    ) as schema_id:
        assert schemas_table.count == schemas_table.snapshot.count + 1
    assert schemas_table.same_as_snapshot


def test_get_schemas(phone_book):
    session = phone_book["session"]
    with SchemaCT(
        session,
        phone_book["service_id"],
        "PhoneBook",
        "/PhoneBook2",
        comments="This is a schema comment",
    ) as schema_id:
        schemas = get_schemas(
            session=phone_book["session"], service_id=phone_book["service_id"]
        )
        assert schemas is not None
        assert isinstance(schemas, list)

        for schema in schemas:
            if schema["id"] == schema_id:
                assert schema == {
                    "comments": "This is a schema comment",
                    "enabled": 1,
                    "host_ctx": "/test",
                    "id": schema_id,
                    "items_per_page": 25,
                    "name": "PhoneBook",
                    "request_path": "/PhoneBook2",
                    "options": None,
                    "metadata": None,
                    "requires_auth": 0,
                    "service_id": phone_book["service_id"],
                    "url_host_id": schemas[1]["url_host_id"],
                    "schema_type": "DATABASE_SCHEMA",
                    "internal": 0,
                }


def test_get_schema(phone_book):
    args = {
        "request_path": "/PhoneBook",
        "schema_name": "PhoneBook",
        "schema_id": phone_book["schema_id"],
        "service_id": phone_book["service_id"],
        "auto_select_single": True,
        "session": phone_book["session"],
    }
    result = get_schema(**args)
    assert result is not None
    assert result == {
        "comments": "test schema",
        "enabled": 1,
        "host_ctx": "/test",
        "id": phone_book["schema_id"],
        "items_per_page": 20,
        "name": "PhoneBook",
        "request_path": "/PhoneBook",
        "requires_auth": 0,
        "service_id": phone_book["service_id"],
        "options": None,
        "metadata": None,
        "url_host_id": phone_book["url_host_id"],
        "schema_type": "DATABASE_SCHEMA",
        "internal": 0,
    }


def test_change_schema(phone_book, table_contents):
    session = phone_book["session"]
    schema_table = table_contents("db_schema")

    args = {
        "schema_name": "PhoneBook",
        "session": phone_book["session"],
    }

    with SchemaCT(
        session, phone_book["service_id"], "PhoneBook", "/test_schema2"
    ) as schema_id:
        schema_table.count == schema_table.snapshot.count + 1
        assert schema_table.get("id", schema_id) == {
            "comments": "",
            "enabled": 1,
            "id": schema_id,
            "items_per_page": 25,
            "name": "PhoneBook",
            "request_path": "/test_schema2",
            "requires_auth": 0,
            "service_id": phone_book["service_id"],
            "options": None,
            "metadata": None,
            "schema_type": "DATABASE_SCHEMA",
            "internal": 0,
        }

        result = disable_schema(schema_id=schema_id, **args)
        assert result is not None
        assert result == True
        assert schema_table.get("id", schema_id)["enabled"] == False

        kwargs = {"schema_id": schema_id, "session": phone_book["session"]}
        schema = get_schema(**kwargs)
        assert schema.get("enabled") == 0

        result = enable_schema(schema_id=schema_id, **args)
        assert result is not None
        assert result == True
        assert schema_table.get("id", schema_id)["enabled"] == True

        schema = get_schema(**kwargs)
        assert schema.get("enabled") == 1

        args["value"] = "PhoneBook2"
        result = set_name(schema_id=schema_id, **args)
        assert result is not None
        assert result == True
        assert schema_table.get("id", schema_id)["name"] == "PhoneBook2"

        schema = get_schema(**kwargs)
        assert schema.get("name") == "PhoneBook2"

        args["schema_name"] = "PhoneBook2"
        args["value"] = "PhoneBook"
        result = set_name(schema_id=schema_id, **args)
        assert result is not None
        assert result == True
        assert schema_table.get("id", schema_id)["name"] == "PhoneBook"

        schema = get_schema(**kwargs)
        assert schema.get("name") == "PhoneBook"
        args["schema_name"] = "PhoneBook"

        # Allow to set the same request_path...will stay unchanged
        args["value"] = schema["request_path"]
        result = set_request_path(schema_id=schema_id, **args)

        # Try to change to an existing request_path
        args["value"] = "/PhoneBook"
        with pytest.raises(Exception) as exc_info:
            result = set_request_path(schema_id=schema_id, **args)
        assert (
            str(exc_info.value)
            == "MySQL Error (1644): The request_path is already used by another entity."
        )

        args["value"] = "/test_schema_4"
        result = set_request_path(schema_id=schema_id, **args)
        assert result is not None
        assert result == True
        schema = get_schema(**kwargs)
        assert schema.get("name") == "PhoneBook"
        assert schema.get("request_path") == args["value"]
        assert schema_table.get("id", schema_id)["request_path"] == "/test_schema_4"

        args["value"] = True
        result = set_require_auth(schema_id=schema_id, **args)
        assert result is not None
        assert result == True
        schema = get_schema(**kwargs)
        assert schema.get("name") == "PhoneBook"
        assert schema.get("requires_auth") == args["value"]
        assert schema_table.get("id", schema_id)["requires_auth"] == True

        args["value"] = 30
        result = set_items_per_page(schema_id=schema_id, **args)
        assert result is not None
        assert result == True
        schema = get_schema(**kwargs)
        assert schema.get("name") == "PhoneBook"
        assert schema.get("items_per_page") == args["value"]
        assert schema_table.get("id", schema_id)["items_per_page"] == 30

        args["value"] = "New comment"
        result = set_comments(schema_id=schema_id, **args)
        assert result is not None
        assert result == True
        assert schema_table.get("id", schema_id)["comments"] == "New comment"

        args["value"] = {
            "name": "PhoneBook3",
            "requires_auth": False,
            "enabled": True,
            "request_path": "/PhoneBook3",
            "items_per_page": 25,
            "comments": "Test comment 6",
        }

        result = update_schema(schema_id=schema_id, **args)
        assert result is not None
        assert result == True
        assert schema_table.get("id", schema_id) == {
            "comments": args["value"]["comments"],
            "enabled": int(args["value"]["enabled"]),
            "id": schema_id,
            "items_per_page": args["value"]["items_per_page"],
            "name": args["value"]["name"],
            "request_path": args["value"]["request_path"],
            "requires_auth": int(args["value"]["requires_auth"]),
            "options": None,
            "service_id": phone_book["service_id"],
            "metadata": None,
            "schema_type": "DATABASE_SCHEMA",
            "internal": 0,
        }

        schema = get_schema(**kwargs)
        assert schema.get("name") == args["value"]["name"]
        assert schema.get("requires_auth") == args["value"]["requires_auth"]
        assert schema.get("enabled") == args["value"]["enabled"]
        assert schema.get("items_per_page") == args["value"]["items_per_page"]
        assert schema.get("comments") == args["value"]["comments"]

        args["value"] = {
            "namexxx": "PhoneBook3",
            "requires_auth": False,
            "enabled": True,
            "request_path": "/PhoneBook3",
            "items_per_page": 25,
            "comments": "Test comment 6",
        }
        with pytest.raises(Exception) as exc_info:
            update_schema(schema_id=schema_id, **args)
        assert str(exc_info.value) == "Attempting to change an invalid schema value."


def test_get_create_statement(phone_book, table_contents):

    sql = get_schema_create_statement(
        schema_id=phone_book["schema_id"],
        session=phone_book["session"],
        include_database_endpoints=True,
    )

    assert sql == schema_create_statement

    sql = get_schema_create_statement(
        schema="/test/PhoneBook",
        session=phone_book["session"],
        include_database_endpoints=True,
    )

    assert sql == schema_create_statement


def test_dump_create_statement(phone_book, table_contents):
    home_file = "~/schema.dump.sql"
    relative_file = "schema.dump.sql"
    full_path_file = os.path.expanduser("~/schema.dump.sql")

    # Test home path
    create_function = lambda file_path, overwrite: store_schema_create_statement(
        file_path=file_path,
        overwrite=overwrite,
        schema_id=phone_book["schema_id"],
        include_database_endpoints=True,
        session=phone_book["session"],
    )

    result = create_function(file_path=home_file, overwrite=True)

    assert result == True

    with open(os.path.expanduser(home_file), "r+") as f:
        assert f.read() == schema_create_statement

    # Test overwrite
    with open(os.path.expanduser(home_file), "a+") as f:
        f.write("<=============================>")

    with pytest.raises(
        Exception,
        match=f"Cancelling operation. File '{os.path.expanduser(home_file)}' already exists.",
    ):
        create_function(file_path=home_file, overwrite=False)

    with open(os.path.expanduser(home_file), "r") as f:
        contents = f.read()
        assert contents.startswith(schema_create_statement)
        assert contents.endswith("<=============================>")

    result = create_function(file_path=home_file, overwrite=True)

    with open(os.path.expanduser(home_file), "r") as f:
        assert f.read() == schema_create_statement

    os.remove(os.path.expanduser(home_file))

    # Test relative path
    if os.path.exists(str(Path.home() / relative_file)):
        os.remove(Path.home() / relative_file)

    result = create_function(file_path=relative_file, overwrite=False)

    assert result == True
    with open(Path.home() / relative_file, "r") as f:
        assert f.read() == schema_create_statement

    # Test absolute path
    if os.path.exists(full_path_file):
        os.remove(full_path_file)

    result = create_function(file_path=full_path_file, overwrite=False)

    assert result == True
    with open(full_path_file, "r") as f:
        assert f.read() == schema_create_statement


def test_dump_and_recover(phone_book, table_contents):
    session = phone_book["session"]
    create_statement = """CREATE OR REPLACE REST SCHEMA /PhoneBook2 ON SERVICE /test
    FROM `PhoneBook`
    AUTHENTICATION NOT REQUIRED;"""
    create_function = (
        lambda file_path, schema_id, overwrite=True: store_schema_create_statement(
            file_path=file_path,
            overwrite=overwrite,
            schema=schema_id,
            include_database_endpoints=False,
            session=session,
        )
    )
    session = phone_book["session"]
    service_id = phone_book["service_id"]

    script = ""

    full_path_file = os.path.expanduser("~/schema_compare_1.dump.sql")
    full_path_file2 = os.path.expanduser("~/schema_compare_2.dump.sql")

    schemas = lib.schemas.get_schemas(session, service_id)
    assert len(schemas) == 3

    with SchemaCT(session, service_id, "PhoneBook", "/PhoneBook2") as schema_id:

        db_object = get_default_db_object_init(
            session, schema_id, name="Addresses", request_path="/addresses"
        )
        with DbObjectCT(session, **db_object) as db_object_id:
            result = create_function(
                file_path=full_path_file, schema_id=schema_id, overwrite=True
            )

            assert result == True

            schemas = lib.schemas.get_schemas(session, service_id)
            assert len(schemas) == 4

    with open(os.path.expanduser(full_path_file), "r+") as f:
        script = f.read()
        assert script == create_statement

    schemas = lib.schemas.get_schemas(session, service_id)
    assert len(schemas) == 3

    with open(full_path_file, "r") as f:
        script = f.read()

    results = lib.script.run_mrs_script(mrs_script=script)

    schemas = lib.schemas.get_schemas(session, service_id)
    assert len(schemas) == 4

    for schema in schemas:
        if schema["request_path"] == "/PhoneBook2":
            create_function(full_path_file2, schema["id"], True)

    for schema in schemas:
        if schema["request_path"] == "/PhoneBook2":
            lib.schemas.delete_schema(session, schema["id"])

    schemas = lib.schemas.get_schemas(session, service_id)
    assert len(schemas) == 3

    with open(full_path_file2, "r") as f:
        assert f.read() == script


def test_dump_and_recover_include_all_objects(phone_book, table_contents):
    session = phone_book["session"]
    create_statement = """CREATE OR REPLACE REST SCHEMA /PhoneBook2 ON SERVICE /test
    FROM `PhoneBook`
    AUTHENTICATION NOT REQUIRED;

CREATE OR REPLACE REST VIEW /addresses
    ON SERVICE /test SCHEMA /PhoneBook2
    AS `PhoneBook`.`Addresses` CLASS MyServicePhoneBookContactsWithEmail @INSERT @UPDATE @DELETE {
        id: id @KEY
    }
    AUTHENTICATION NOT REQUIRED
    ITEMS PER PAGE 10
    COMMENT 'Object that will be removed'
    MEDIA TYPE 'application/json'
    OPTIONS {
        "aaa": "val aaa",
        "bbb": "val bbb"
    };"""
    create_function = (
        lambda file_path, schema_id, overwrite=True: store_schema_create_statement(
            file_path=file_path,
            overwrite=overwrite,
            schema=schema_id,
            include_database_endpoints=True,
            session=session,
        )
    )
    session = phone_book["session"]
    service_id = phone_book["service_id"]

    script = ""

    full_path_file = os.path.expanduser("~/schema_compare_1.dump.sql")
    full_path_file2 = os.path.expanduser("~/schema_compare_2.dump.sql")

    schemas = lib.schemas.get_schemas(session, service_id)
    assert len(schemas) == 3

    with SchemaCT(session, service_id, "PhoneBook", "/PhoneBook2") as schema_id:

        db_object = get_default_db_object_init(
            session, schema_id, name="Addresses", request_path="/addresses"
        )
        with DbObjectCT(session, **db_object) as db_object_id:
            result = create_function(
                file_path=full_path_file, schema_id=schema_id, overwrite=True
            )

            assert result == True

            schemas = lib.schemas.get_schemas(session, service_id)
            assert len(schemas) == 4

    with open(os.path.expanduser(full_path_file), "r+") as f:
        script = f.read()
        assert script == create_statement

    schemas = lib.schemas.get_schemas(session, service_id)
    assert len(schemas) == 3

    with open(full_path_file, "r") as f:
        script = f.read()

    results = lib.script.run_mrs_script(mrs_script=script)

    schemas = lib.schemas.get_schemas(session, service_id)
    assert len(schemas) == 4

    for schema in schemas:
        if schema["request_path"] == "/PhoneBook2":
            create_function(full_path_file2, schema["id"], True)

    for schema in schemas:
        if schema["request_path"] == "/PhoneBook2":
            lib.schemas.delete_schema(session, schema["id"])

    schemas = lib.schemas.get_schemas(session, service_id)
    assert len(schemas) == 3

    with open(full_path_file2, "r") as f:
        assert f.read() == script
