# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import os
import sys
import tempfile
from pathlib import Path

import mozfile

sys.path.append(os.fspath(Path(__file__).parents[0]))
from backup_test_base import BackupTestBase


class BackupSelectableToLegacyTest(BackupTestBase):
    """
    Tests that a backup created from a selectable profile can be recovered
    into a legacy profile environment, converting it to selectable.

    The recovered profile should have the same groupID (storeID) as the
    profile group created during conversion.
    """

    def test_backup_selectable_to_legacy(self):
        self.logger.info("=== Test: Selectable -> Legacy ===")

        self.logger.info("Step 1: Setting up source selectable profile")
        profile_name = self.register_profile_and_restart()
        self._cleanups.append({"profile_name": profile_name})
        self.logger.info(f"Registered toolkit profile: {profile_name}")

        selectable_info = self.setup_selectable_profile()
        original_backup_store_id = selectable_info["store_id"]
        self.assertIsNotNone(
            original_backup_store_id, "Backup profile should have storeID"
        )
        self.logger.info(
            f"Created selectable profile with storeID: {original_backup_store_id}"
        )

        self.set_prefs({"test.selectable.backup.pref": "test-value"})

        self.marionette.restart(clean=False, in_app=True)

        self.logger.info("Step 2: Creating backup from selectable profile")
        self._archive_path = self.create_backup()
        self._cleanups.append({"path": self._archive_path})
        self.assertTrue(
            os.path.exists(self._archive_path), "Backup archive should exist"
        )
        self.logger.info(f"Backup created at: {self._archive_path}")

        self.logger.info("Step 3: Switching to new legacy profile environment")
        self.marionette.quit()
        self.marionette.instance.switch_profile()
        self.marionette.start_session()
        self.marionette.set_context("chrome")

        legacy_profile_name = self.register_profile_and_restart()
        self._cleanups.append({"profile_name": legacy_profile_name})
        self.logger.info(f"Created legacy profile: {legacy_profile_name}")

        stale_store_id = "stale-garbage-store-id-12345"
        self.set_prefs({
            "browser.profiles.enabled": True,
            "browser.profiles.created": False,
            "toolkit.profiles.storeID": stale_store_id,
        })
        self.logger.info(f"Set stale storeID on legacy profile: {stale_store_id}")

        has_selectable = self.has_selectable_profiles()
        self.assertFalse(has_selectable, "Should start as legacy profile")
        self.logger.info(
            "Verified profile is legacy (hasCreatedSelectableProfiles=False)"
        )

        pre_recovery_store_id = self.get_store_id_pref()
        self.assertEqual(
            pre_recovery_store_id,
            stale_store_id,
            "Legacy profile should have the stale storeID we set",
        )

        self.logger.info("Step 4: Recovering backup into legacy environment")
        self._recovery_path = os.path.join(
            tempfile.gettempdir(), "selectable-to-legacy-recovery"
        )
        mozfile.remove(self._recovery_path)
        self._cleanups.append({"path": self._recovery_path})

        recovery_result = self.recover_backup(self._archive_path, self._recovery_path)
        self._new_profile_path = recovery_result["path"]
        self._new_profile_id = recovery_result["id"]
        self._cleanups.append({"path": self._new_profile_path})
        self.logger.info(
            f"Recovery complete. New profile path: {self._new_profile_path}, id: {self._new_profile_id}"
        )

        self.logger.info("Step 5: Verifying legacy profile was converted to selectable")
        has_selectable_after = self.has_selectable_profiles()
        self.assertTrue(
            has_selectable_after,
            "Legacy profile should be converted to selectable after recovery",
        )
        self.logger.info(
            "Legacy profile converted to selectable (hasCreatedSelectableProfiles=True)"
        )

        self.logger.info("Step 6: Verifying database entries")
        original_profile_name = self.get_original_profile_name()
        profiles_after_conversion = self.get_all_profiles()
        self.logger.info(f"Profiles in database: {profiles_after_conversion}")
        self.assertEqual(
            len(profiles_after_conversion),
            2,
            "Database should have 2 profiles after recovery (original + recovered)",
        )

        original_profile = next(
            (
                p
                for p in profiles_after_conversion
                if p["name"] == original_profile_name
            ),
            None,
        )
        self.assertIsNotNone(
            original_profile,
            f"Database should have a profile named '{original_profile_name}' (the converted legacy profile)",
        )
        self.logger.info(f"Found 'Original Profile' in database: {original_profile}")

        recovered_profile_in_db = next(
            (p for p in profiles_after_conversion if p["id"] == self._new_profile_id),
            None,
        )
        self.assertIsNotNone(
            recovered_profile_in_db,
            "Recovered profile should exist in the database",
        )
        self.logger.info(
            f"Found recovered profile in database: {recovered_profile_in_db}"
        )

        self.logger.info("Step 7: Verifying storeID was updated (not stale)")
        converted_store_id = self.get_store_id()
        self.logger.info(f"Converted storeID: {converted_store_id}")
        self.assertIsNotNone(
            converted_store_id, "Converted profile should have storeID"
        )
        self.assertNotEqual(
            converted_store_id,
            original_backup_store_id,
            "Converted profile group should have a NEW storeID, not the backup's",
        )
        self.assertNotEqual(
            converted_store_id,
            stale_store_id,
            "Converted profile group should have a NEW storeID, not the stale one",
        )
        self.logger.info(
            f"Verified storeID changed from stale '{stale_store_id}' to new '{converted_store_id}'"
        )

        self.logger.info("Step 8: Launching recovered profile and verifying prefs")
        self.marionette.quit()
        intermediate_profile = self.marionette.instance.profile
        self.marionette.instance.profile = self._new_profile_path
        self.marionette.start_session()
        self.marionette.set_context("chrome")

        self.wait_for_post_recovery()
        self.logger.info("Post-recovery complete")

        self.init_selectable_profile_service()

        recovered_store_id = self.get_store_id()
        self.assertEqual(
            recovered_store_id,
            converted_store_id,
            "Recovered profile should have same storeID as converted profile group",
        )
        self.logger.info(
            f"Recovered profile storeID matches converted profile group: {recovered_store_id}"
        )

        store_id_pref = self.get_store_id_pref()
        self.assertEqual(
            store_id_pref,
            converted_store_id,
            "toolkit.profiles.storeID pref should match the profile group storeID",
        )
        self.assertNotEqual(
            store_id_pref,
            stale_store_id,
            "toolkit.profiles.storeID pref should NOT be the stale value",
        )
        self.logger.info(f"toolkit.profiles.storeID pref verified: {store_id_pref}")

        self.logger.info("Step 9: Cleaning up")
        self.marionette.quit()
        self.marionette.instance.profile = intermediate_profile
        self.marionette.start_session()
        self.marionette.set_context("chrome")

        self.cleanup_selectable_profiles()
        self.logger.info("=== Test: Selectable -> Legacy PASSED ===")

    def test_backup_selectable_to_legacy_replace(self):
        """
        Tests that recovering a selectable backup into a legacy profile with
        replaceCurrentProfile=true converts the legacy profile to selectable
        and then deletes the original profile.
        """
        self.logger.info("=== Test: Selectable -> Legacy (Replace) ===")

        self.logger.info("Step 1: Setting up source selectable profile")
        profile_name = self.register_profile_and_restart()
        self._cleanups.append({"profile_name": profile_name})

        selectable_info = self.setup_selectable_profile()
        original_backup_store_id = selectable_info["store_id"]
        self.assertIsNotNone(
            original_backup_store_id, "Backup profile should have storeID"
        )
        self.logger.info(
            f"Created selectable profile with storeID: {original_backup_store_id}"
        )

        self.set_prefs({"test.selectable.replace.pref": "replace-test-value"})
        self.marionette.restart(clean=False, in_app=True)

        self.logger.info("Step 2: Creating backup from selectable profile")
        self._archive_path = self.create_backup()
        self._cleanups.append({"path": self._archive_path})
        self.assertTrue(
            os.path.exists(self._archive_path), "Backup archive should exist"
        )
        self.logger.info(f"Backup created at: {self._archive_path}")

        self.logger.info("Step 3: Switching to new legacy profile environment")
        self.marionette.quit()
        self.marionette.instance.switch_profile()
        self.marionette.start_session()
        self.marionette.set_context("chrome")

        legacy_profile_name = self.register_profile_and_restart()
        self._cleanups.append({"profile_name": legacy_profile_name})
        self.logger.info(f"Created legacy profile: {legacy_profile_name}")

        self.set_prefs({
            "browser.profiles.enabled": True,
            "browser.profiles.created": False,
        })

        has_selectable = self.has_selectable_profiles()
        self.assertFalse(has_selectable, "Should start as legacy profile")
        self.logger.info("Verified profile is legacy")

        self.logger.info("Step 4: Recovering backup with replaceCurrentProfile=true")
        self._recovery_path = os.path.join(
            tempfile.gettempdir(), "selectable-to-legacy-replace-recovery"
        )
        mozfile.remove(self._recovery_path)
        self._cleanups.append({"path": self._recovery_path})

        recovery_result = self.recover_backup(
            self._archive_path, self._recovery_path, replace_current_profile=True
        )
        self._new_profile_path = recovery_result["path"]
        self._new_profile_id = recovery_result["id"]
        self._cleanups.append({"path": self._new_profile_path})
        self.logger.info(
            f"Recovery complete. New profile path: {self._new_profile_path}"
        )

        self.logger.info("Step 5: Verifying only the recovered profile remains")
        profiles_after = self.get_all_profiles()
        self.logger.info(f"Profiles in database: {profiles_after}")

        self.assertEqual(
            len(profiles_after),
            1,
            "Database should have only 1 profile (original was deleted after replace)",
        )

        recovered_profile = profiles_after[0]
        self.assertEqual(
            recovered_profile["id"],
            self._new_profile_id,
            "The only remaining profile should be the recovered one",
        )
        self.logger.info(
            f"Verified only recovered profile remains: {recovered_profile}"
        )

        self.logger.info("Step 7: Cleaning up")
        self.cleanup_selectable_profiles()
        self.logger.info("=== Test: Selectable -> Legacy (Replace) PASSED ===")

    def test_backup_selectable_to_disabled_selectable(self):
        """
        Tests that a backup created from a selectable profile can be recovered
        into a legacy profile where selectable profiles are NOT enabled.

        The stale selectable profile prefs from the backup should be reset to
        their default/falsey values.
        """
        self.logger.info("=== Test: Selectable -> Disabled Selectable ===")

        self.logger.info("Step 1: Setting up source selectable profile")
        profile_name = self.register_profile_and_restart()
        self._cleanups.append({"profile_name": profile_name})

        selectable_info = self.setup_selectable_profile()
        original_backup_store_id = selectable_info["store_id"]
        self.assertIsNotNone(
            original_backup_store_id, "Backup profile should have storeID"
        )
        self.logger.info(
            f"Created selectable profile with storeID: {original_backup_store_id}"
        )

        self.set_prefs({"test.selectable.disabled.pref": "test-value"})
        self.marionette.restart(clean=False, in_app=True)

        self.logger.info("Step 2: Creating backup from selectable profile")
        self._archive_path = self.create_backup()
        self._cleanups.append({"path": self._archive_path})
        self.assertTrue(
            os.path.exists(self._archive_path), "Backup archive should exist"
        )
        self.logger.info(f"Backup created at: {self._archive_path}")

        self.logger.info(
            "Step 3: Switching to new legacy profile with selectable profiles disabled"
        )
        self.marionette.quit()
        self.marionette.instance.switch_profile()
        self.marionette.start_session()
        self.marionette.set_context("chrome")

        legacy_profile_name = self.register_profile_and_restart()
        self._cleanups.append({"profile_name": legacy_profile_name})
        self.logger.info(f"Created legacy profile: {legacy_profile_name}")

        self.set_prefs({
            "browser.profiles.enabled": False,
            "browser.profiles.created": False,
        })

        has_selectable = self.has_selectable_profiles()
        self.assertFalse(has_selectable, "Should start as legacy profile")
        self.logger.info("Verified profile is legacy with selectable profiles disabled")

        self.logger.info("Step 4: Recovering backup into disabled environment")
        self._recovery_path = os.path.join(
            tempfile.gettempdir(), "selectable-to-disabled-recovery"
        )
        mozfile.remove(self._recovery_path)
        self._cleanups.append({"path": self._recovery_path})

        recovery_result = self.recover_backup(self._archive_path, self._recovery_path)
        self._new_profile_path = recovery_result["path"]
        self._cleanups.append({"path": self._new_profile_path})
        self.logger.info(
            f"Recovery complete. New profile path: {self._new_profile_path}"
        )

        self.logger.info(
            "Step 5: Launching recovered profile and verifying prefs are reset"
        )
        self.marionette.quit()
        intermediate_profile = self.marionette.instance.profile
        self.marionette.instance.profile = self._new_profile_path
        self.marionette.start_session()
        self.marionette.set_context("chrome")

        self.wait_for_post_recovery()
        self.logger.info("Post-recovery complete")

        profiles_enabled = self.run_code(
            """return Services.prefs.getBoolPref("browser.profiles.enabled", null);"""
        )
        self.assertFalse(
            profiles_enabled,
            "browser.profiles.enabled should be false",
        )

        profiles_created = self.run_code(
            """return Services.prefs.getBoolPref("browser.profiles.created", null);"""
        )
        self.assertFalse(
            profiles_created,
            "browser.profiles.created should be false",
        )

        test_pref = self.run_code(
            """return Services.prefs.getStringPref("test.selectable.disabled.pref", null);"""
        )
        self.assertEqual(
            test_pref,
            "test-value",
            "User prefs from backup should be preserved",
        )
        self.logger.info("Verified user prefs from backup are preserved")

        self.logger.info("Step 6: Cleaning up")
        self.marionette.quit()
        self.marionette.instance.profile = intermediate_profile
        self.marionette.start_session()
        self.marionette.set_context("chrome")

        self.logger.info("=== Test: Selectable -> Disabled Selectable PASSED ===")
