# Copyright (c) 2025, 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 os
from pathlib import Path
import tempfile
import shutil
import pytest
from msm_plugin.management import *

SCHEMA_NAME = "my_schema"
COPYRIGHT_HOLDER = "Oracle and/or its affiliates."

MSM_SECTION_140_SQL_CONTENT_001 = """
CREATE TABLE `my_schema`.`my_1st_table`(
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `name1` VARCHAR(255)
);"""

MSM_SECTION_140_SQL_CONTENT_002 = """
CREATE TABLE `my_schema`.`my_2nd_table`(
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `name2` VARCHAR(255)
);
"""

MSM_SECTION_150_SQL_CONTENT_002 = r"""
DELIMITER %%

DROP PROCEDURE IF EXISTS `my_schema`.`my_1st_proc`%%
CREATE PROCEDURE `my_schema`.`my_1st_proc`(INOUT val INT)
BEGIN
    SET val = val + 1;
END%%

DELIMITER ;
"""

MSM_SECTION_140_SQL_CONTENT_003 = """
CREATE TABLE `my_schema`.`my_3nd_table`(
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `name3` VARCHAR(255)
);
"""


def test_msm_sections():
    tests_folder = Path(__file__).parent.parent

    with tempfile.TemporaryDirectory() as temp_dir:
        # temp_dir = os.path.join(os.path.expanduser("~"), "Documents", "temp")
        # Create a new MSM project
        project_path = create_new_project_folder(
            schema_name=SCHEMA_NAME,
            target_path=temp_dir,
            copyright_holder=COPYRIGHT_HOLDER,
        )

        # Update the development file with the first CREATE TABLE
        dev_file_path = lib.management.get_schema_development_file_path(
            schema_project_path=project_path
        )

        set_section_sql_content(
            file_path=dev_file_path,
            section_id="140",
            sql_content=MSM_SECTION_140_SQL_CONTENT_001,
        )

        # Check if the content was written to the file
        sql_content = get_sql_content_from_section(
            file_path=dev_file_path, section_id="140"
        )

        assert sql_content == MSM_SECTION_140_SQL_CONTENT_001.strip()

        # Add more content and check if the content was updated correctly
        set_section_sql_content(
            file_path=dev_file_path,
            section_id="140",
            sql_content=(
                MSM_SECTION_140_SQL_CONTENT_001 + "\n" + MSM_SECTION_140_SQL_CONTENT_002
            ),
        )

        sql_content = get_sql_content_from_section(
            file_path=dev_file_path, section_id="140"
        )

        assert (
            sql_content
            == (
                MSM_SECTION_140_SQL_CONTENT_001 + "\n" + MSM_SECTION_140_SQL_CONTENT_002
            ).strip()
        )


def test_create_new_project_folder():
    with tempfile.TemporaryDirectory() as temp_dir:
        # temp_dir = os.path.join(os.path.expanduser("~"), "Documents", "temp")

        project_path = create_new_project_folder(
            schema_name=SCHEMA_NAME,
            target_path=temp_dir,
            copyright_holder=COPYRIGHT_HOLDER,
        )

        assert os.path.exists(project_path)

        assert os.path.exists(os.path.join(project_path, "README.md"))
        assert os.path.exists(os.path.join(project_path, "msm.project.json"))

        project_settings = get_project_settings(schema_project_path=project_path)
        assert project_settings.get("copyrightHolder", None) == COPYRIGHT_HOLDER

        project_info = get_project_information(schema_project_path=project_path)

        current_dev_version = project_info.get("currentDevelopmentVersion", None)
        assert current_dev_version == "0.0.1"


def test_set_development_version():
    with tempfile.TemporaryDirectory() as temp_dir:
        # temp_dir = os.path.join(os.path.expanduser("~"), "Documents", "temp")

        project_path = create_new_project_folder(
            schema_name=SCHEMA_NAME,
            target_path=temp_dir,
            copyright_holder=COPYRIGHT_HOLDER,
        )

        # Set the development version to 0.0.2
        set_development_version(schema_project_path=project_path, version="0.0.2")
        project_info = get_project_information(schema_project_path=project_path)
        current_dev_version = project_info.get("currentDevelopmentVersion", None)
        assert current_dev_version == "0.0.2"

        # Set the development version back to 0.0.1
        set_development_version(schema_project_path=project_path, version="0.0.1")
        project_info = get_project_information(schema_project_path=project_path)
        current_dev_version = project_info.get("currentDevelopmentVersion", None)
        assert current_dev_version == "0.0.1"

        # Check that there are no released versions
        released_version = get_released_versions(schema_project_path=project_path)
        assert len(released_version) == 0


def test_prepare_release(sandbox_session, project_path):
    # Since the project has just been created, there is no deployment script yet
    with pytest.raises(Exception):
        generate_deployment_script(schema_project_path=project_path)

    # ----------------------------------------------------------------------
    # Write some SQL to the development/my_schema_next.sql file
    dev_sql_file_path = os.path.join(project_path, "development", "my_schema_next.sql")
    set_section_sql_content(
        file_path=dev_sql_file_path,
        section_id="140",
        sql_content=MSM_SECTION_140_SQL_CONTENT_001,
    )

    files_for_release = prepare_release(
        schema_project_path=project_path, version="0.0.1", next_version="0.0.2"
    )

    # Since this is the first release, there will only be the versions/my_schema_0.0.1.sql file
    # and no updates file
    assert len(files_for_release) == 1

    released_version = get_released_versions(schema_project_path=project_path)
    assert len(released_version) == 1

    last_released_version = get_last_released_version(schema_project_path=project_path)
    assert last_released_version == [0, 0, 1]

    # Check if the content of the version SQL file has the right content
    version_sql_file_path = os.path.join(
        project_path, "releases", "versions", "my_schema_0.0.1.sql"
    )
    sql_content_001 = get_sql_content_from_section(
        file_path=dev_sql_file_path, section_id="140"
    )
    assert sql_content_001 == MSM_SECTION_140_SQL_CONTENT_001.strip()

    # Generate deployment script
    deployment_sql_file_path = generate_deployment_script(
        schema_project_path=project_path
    )

    # Check if the schema and the table have been created
    assert sandbox_session is not None

    lib.core.execute_msm_sql_script(
        session=sandbox_session, sql_file_path=deployment_sql_file_path
    )

    assert (
        lib.core.MsmDbExec(
            "SELECT COUNT(*) as schema_count FROM information_schema.SCHEMATA "
            "WHERE SCHEMA_NAME = ?"
        )
        .exec(sandbox_session, [SCHEMA_NAME])
        .first["schema_count"]
    ) == 1

    assert (
        lib.core.MsmDbExec(
            "SELECT COUNT(*) as table_count FROM information_schema.TABLES "
            "WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = ?"
        )
        .exec(sandbox_session, [SCHEMA_NAME, "BASE TABLE"])
        .first["table_count"]
    ) == 1

    assert (
        lib.core.MsmDbExec(
            "SELECT COUNT(*) as table_count FROM information_schema.TABLES "
            "WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = ?"
        )
        .exec(sandbox_session, [SCHEMA_NAME, "VIEW"])
        .first["table_count"]
    ) == 1

    # ----------------------------------------------------------------------
    # Add more SQL to the development/my_schema_next.sql file

    # Add another table
    sql_content_dev = get_sql_content_from_section(
        file_path=dev_sql_file_path, section_id="140"
    )
    sql_content_dev_140 = sql_content_dev + "\n" + MSM_SECTION_140_SQL_CONTENT_002
    set_section_sql_content(
        file_path=dev_sql_file_path, section_id="140", sql_content=sql_content_dev_140
    )

    # Add a stored procedure
    sql_content_dev = get_sql_content_from_section(
        file_path=dev_sql_file_path, section_id="150"
    )
    set_section_sql_content(
        file_path=dev_sql_file_path,
        section_id="150",
        sql_content=MSM_SECTION_150_SQL_CONTENT_002,
    )

    # Prepare 0.0.2 release
    prepare_release(
        schema_project_path=project_path, version="0.0.2", next_version="0.0.3"
    )

    # Check the content of the versions/0.0.2 SQL file section 140
    version_sql_file_path = os.path.join(
        project_path, "releases", "versions", "my_schema_0.0.2.sql"
    )
    sql_content_002 = get_sql_content_from_section(
        file_path=version_sql_file_path, section_id="140"
    )
    assert sql_content_002 == sql_content_dev_140.strip()

    # Set the upgrade code in the 0.0.1 to 0.0.2 update file
    update_sql_file_path = os.path.join(
        project_path, "releases", "updates", "my_schema_0.0.1_to_0.0.2.sql"
    )
    set_section_sql_content(
        file_path=update_sql_file_path,
        section_id="240",
        sql_content=MSM_SECTION_140_SQL_CONTENT_002,
    )

    update_sql_file_path = os.path.join(
        project_path, "releases", "updates", "my_schema_0.0.1_to_0.0.2.sql"
    )
    set_section_sql_content(
        file_path=update_sql_file_path,
        section_id="250",
        sql_content=MSM_SECTION_150_SQL_CONTENT_002,
    )

    # Generate deployment script
    deployment_sql_file_path = generate_deployment_script(
        schema_project_path=project_path
    )

    # Run deployment script
    lib.core.execute_msm_sql_script(
        session=sandbox_session, sql_file_path=deployment_sql_file_path
    )

    # Check that there are now two tables
    assert (
        lib.core.MsmDbExec(
            "SELECT COUNT(*) as table_count FROM information_schema.TABLES "
            "WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = ?"
        )
        .exec(sandbox_session, [SCHEMA_NAME, "BASE TABLE"])
        .first["table_count"]
    ) == 2

    assert (
        lib.core.MsmDbExec(
            "SELECT COUNT(*) as table_count FROM information_schema.TABLES "
            "WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = ?"
        )
        .exec(sandbox_session, [SCHEMA_NAME, "VIEW"])
        .first["table_count"]
    ) == 1

    assert (
        lib.core.MsmDbExec(
            "SELECT COUNT(*) as proc_count FROM information_schema.ROUTINES "
            "WHERE ROUTINE_SCHEMA = ? AND ROUTINE_TYPE = ?"
        )
        .exec(sandbox_session, [SCHEMA_NAME, "PROCEDURE"])
        .first["proc_count"]
    ) == 1

    # ----------------------------------------------------------------------
    # Prepare 0.0.3 Release

    # Add more SQL to the development/my_schema_next.sql file
    sql_content_dev = get_sql_content_from_section(
        file_path=dev_sql_file_path, section_id="140"
    )
    sql_content_dev = sql_content_dev + "\n" + MSM_SECTION_140_SQL_CONTENT_003

    set_section_sql_content(
        file_path=dev_sql_file_path, section_id="140", sql_content=sql_content_dev
    )

    # Prepare the 0.0.3 release
    prepare_release(
        schema_project_path=project_path, version="0.0.3", next_version="0.0.4"
    )

    # Set the upgrade code in the 0.0.2 to 0.0.3 update file
    update_sql_file_path = os.path.join(
        project_path, "releases", "updates", "my_schema_0.0.2_to_0.0.3.sql"
    )
    set_section_sql_content(
        file_path=update_sql_file_path,
        section_id="240",
        sql_content=MSM_SECTION_140_SQL_CONTENT_003,
    )

    # Generate deployment script
    deployment_sql_file_path = generate_deployment_script(
        schema_project_path=project_path
    )

    # Run deployment script
    lib.core.execute_msm_sql_script(
        session=sandbox_session, sql_file_path=deployment_sql_file_path
    )

    # Check that there are now two tables
    assert (
        lib.core.MsmDbExec(
            "SELECT COUNT(*) as table_count FROM information_schema.TABLES "
            "WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = ?"
        )
        .exec(sandbox_session, [SCHEMA_NAME, "BASE TABLE"])
        .first["table_count"]
    ) == 3

    assert (
        lib.core.MsmDbExec(
            "SELECT COUNT(*) as table_count FROM information_schema.TABLES "
            "WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = ?"
        )
        .exec(sandbox_session, [SCHEMA_NAME, "VIEW"])
        .first["table_count"]
    ) == 1


def test_deployment(sandbox_session, project_path):
    # Ensure to start fresh
    lib.core.MsmDbExec(
        f"DROP SCHEMA IF EXISTS {lib.core.quote_ident(SCHEMA_NAME)}"
    ).exec(sandbox_session)

    # Deploy all released versions after each other to test update capability
    released_versions = get_released_versions(schema_project_path=project_path)

    assert len(released_versions) > 0

    version_str = "%d.%d.%d" % tuple(released_versions[0])
    result_msg = deploy_schema(schema_project_path=project_path, version=version_str)

    assert result_msg == (
        f"Deployment of `{SCHEMA_NAME}` version "
        f"{version_str} completed successfully."
    )

    assert len(released_versions) > 1

    version_str_next = "%d.%d.%d" % tuple(released_versions[1])
    result_msg = deploy_schema(
        schema_project_path=project_path, version=version_str_next
    )

    assert result_msg == (
        f"Completed the update of `{SCHEMA_NAME}` version "
        f"{version_str} to {version_str_next} successfully."
    )

    # for version in released_versions:
    #     version_str = '%d.%d.%d' % tuple(version)
    #     deploy_schema(schema_project_path=project_path, version=version_str)

    #     # Check if the right version has actually been deployed
    #     assert (lib.core.MsmDbExec(
    #         "SELECT CONCAT(major, '.', minor, '.', patch) AS version "
    #         f"FROM {lib.core.quote_ident(SCHEMA_NAME)}.`msm_schema_version`")
    #         .exec(sandbox_session).first["version"]) == version_str
