"""Privileged licensing maintenance command safety tests."""

from __future__ import annotations

import argparse
import json
import os
from pathlib import Path
import tempfile
import unittest
from unittest.mock import Mock, patch

from licensing_server import cli as licensing_cli
from licensing_server.app.config import (
    NotificationWorkerSettings,
    SubscriptionWorkerSettings,
)
from licensing_server.cli import _parser, _write_owner_only_serial_export


class LicensingCliTests(unittest.TestCase):
    def test_serial_generation_accepts_profile_and_bounded_trial_duration(self) -> None:
        parser = _parser()
        generated = parser.parse_args(
            [
                "serials",
                "generate",
                "--sku",
                "trial.full",
                "--quantity",
                "2",
                "--admin-user-id",
                "user.test.admin",
                "--reason",
                "One-hour QA cohort",
                "--surface-profile-id",
                "surface_profile.test.focused",
                "--trial-duration-hours",
                "1",
                "--output",
                "trial-serials.json",
            ]
        )
        self.assertEqual(generated.surface_profile_id, "surface_profile.test.focused")
        self.assertEqual(generated.trial_duration_hours, 1)
        self.assertIs(generated.handler, licensing_cli._generate_serials)

    def test_signing_key_compromise_requires_exact_target_and_confirmation(self) -> None:
        parser = _parser()
        preview = parser.parse_args(
            [
                "signing",
                "mark-compromised",
                "--key-id",
                "license.test.compromised",
                "--admin-user-id",
                "user.test.signing_admin",
                "--reason-code",
                "compromise_response",
            ]
        )
        self.assertEqual(preview.key_id, "license.test.compromised")
        self.assertFalse(preview.confirm)
        self.assertIsNone(preview.expected_state_digest)
        confirmed = parser.parse_args(
            [
                "signing",
                "mark-compromised",
                "--key-id",
                "license.test.compromised",
                "--admin-user-id",
                "user.test.signing_admin",
                "--reason-code",
                "compromise_response",
                "--expected-state-digest",
                "ab" * 32,
                "--confirm",
            ]
        )
        self.assertTrue(confirmed.confirm)
        self.assertEqual(confirmed.expected_state_digest, "ab" * 32)

    def test_deployment_doctor_uses_only_selected_component_loader(self) -> None:
        cases = (
            (
                "subscription_worker",
                "_subscription_worker_settings",
                SubscriptionWorkerSettings(
                    database_url="sqlite+pysqlite:///:memory:",
                    environment_name="development",
                    stripe_secret_key="sk_test_component",
                ),
            ),
            (
                "notification_worker",
                "_notification_worker_settings",
                NotificationWorkerSettings(
                    database_url="sqlite+pysqlite:///:memory:",
                    environment_name="development",
                ),
            ),
        )
        for component, loader_name, settings in cases:
            with self.subTest(component=component):
                unselected_loader_name = (
                    "_notification_worker_settings"
                    if loader_name == "_subscription_worker_settings"
                    else "_subscription_worker_settings"
                )
                engine = Mock()
                report = Mock()
                report.ready = True
                report.to_mapping.return_value = {"status": "ready"}
                arguments = argparse.Namespace(
                    component=component,
                    nonproduction_diagnostic=True,
                    require_commerce=False,
                    maximum_backlog_age_minutes=15,
                    output=None,
                )
                with (
                    patch.object(
                        licensing_cli,
                        loader_name,
                        return_value=settings,
                    ) as selected_loader,
                    patch.object(
                        licensing_cli,
                        "_settings",
                        side_effect=AssertionError("API settings must not load"),
                    ),
                    patch.object(
                        licensing_cli,
                        unselected_loader_name,
                        side_effect=AssertionError(
                            "unselected component settings must not load"
                        ),
                    ),
                    patch.object(
                        licensing_cli,
                        "create_database_engine",
                        return_value=engine,
                    ),
                    patch.object(
                        licensing_cli,
                        "inspect_deployment_readiness",
                        return_value=report,
                    ) as inspect_readiness,
                ):
                    result = licensing_cli._deployment_doctor(arguments)
                self.assertEqual(result, licensing_cli.CLI_SUCCESS)
                selected_loader.assert_called_once_with()
                inspect_readiness.assert_called_once()
                self.assertIs(inspect_readiness.call_args.args[1], settings)
                self.assertIsNone(inspect_readiness.call_args.args[3])
                engine.dispose.assert_called_once_with()

    def test_deployment_doctor_requires_explicit_capability_gates(self) -> None:
        parser = _parser()
        doctor = parser.parse_args(
            [
                "deployment",
                "doctor",
                "--maximum-backlog-age-minutes",
                "30",
                "--component",
                "api",
                "--require-commerce",
                "--output",
                "deployment-evidence.json",
            ]
        )
        self.assertEqual(doctor.maximum_backlog_age_minutes, 30)
        self.assertEqual(doctor.component, "api")
        self.assertTrue(doctor.require_commerce)
        self.assertFalse(doctor.nonproduction_diagnostic)
        self.assertEqual(doctor.output, "deployment-evidence.json")
        notification_doctor = parser.parse_args(
            ["deployment", "doctor", "--component", "notification_worker"]
        )
        self.assertEqual(notification_doctor.component, "notification_worker")
        self.assertFalse(notification_doctor.require_commerce)

    def test_notification_worker_commands_are_explicit_and_bounded(self) -> None:
        parser = _parser()
        worker = parser.parse_args(
            [
                "notifications",
                "worker",
                "--maximum-events",
                "25",
                "--maximum-age-days",
                "5",
                "--claim-timeout-minutes",
                "20",
                "--poll-seconds",
                "2",
            ]
        )
        self.assertEqual(worker.maximum_events, 25)
        self.assertEqual(worker.maximum_age_days, 5)
        self.assertEqual(worker.claim_timeout_minutes, 20)
        self.assertEqual(worker.poll_seconds, 2.0)
        retry = parser.parse_args(
            ["notifications", "retry-failed", "--maximum-attempts", "7"]
        )
        self.assertEqual(retry.maximum_attempts, 7)
        feedback = parser.parse_args(
            [
                "notifications",
                "record-feedback",
                "--provider",
                "smtp.test",
                "--provider-event-id",
                "feedback.test.one",
                "--feedback-type",
                "hard_bounce",
                "--occurred-at",
                "2026-08-16T12:00:00Z",
                "--delivery-id",
                "notification_delivery.test.one",
            ]
        )
        self.assertEqual(feedback.feedback_type, "hard_bounce")
        self.assertEqual(
            feedback.delivery_id,
            "notification_delivery.test.one",
        )
        clear = parser.parse_args(
            [
                "notifications",
                "clear-suppression",
                "--user-id",
                "user.test.one",
                "--admin-user-id",
                "user.test.admin",
                "--reason",
                "customer_request",
            ]
        )
        self.assertEqual(clear.reason, "customer_request")
        purge = parser.parse_args(
            ["notifications", "purge-feedback", "--retention-days", "400"]
        )
        self.assertEqual(purge.retention_days, 400)

    def test_signing_commands_separate_staging_export_and_guarded_activation(self) -> None:
        parser = _parser()
        status = parser.parse_args(["signing", "status"])
        self.assertIs(status.handler, licensing_cli._signing_status)
        register = parser.parse_args(
            [
                "signing",
                "register-current",
                "--admin-user-id",
                "user.test.admin",
                "--reason-code",
                "planned_rotation",
                "--not-before",
                "2026-08-16T00:00:00Z",
                "--expires-at",
                "2029-08-16T00:00:00Z",
            ]
        )
        self.assertEqual(register.reason_code, "planned_rotation")
        self.assertEqual(register.not_before, "2026-08-16T00:00:00Z")
        activate = parser.parse_args(
            [
                "signing",
                "activate-current",
                "--admin-user-id",
                "user.test.admin",
                "--reason-code",
                "planned_rotation",
                "--expected-state-digest",
                "ab" * 32,
                "--confirm",
            ]
        )
        self.assertTrue(activate.confirm)
        self.assertEqual(activate.expected_state_digest, "ab" * 32)
        export = parser.parse_args(
            ["signing", "export-public-key", "--output", "public-key.json"]
        )
        self.assertIs(export.handler, licensing_cli._export_signing_public_key)

    def test_catalog_commands_separate_staging_preview_and_publication(self) -> None:
        parser = _parser()
        status = parser.parse_args(["catalog", "status"])
        self.assertIs(status.handler, licensing_cli._catalog_status)
        stage = parser.parse_args(
            [
                "catalog",
                "stage-current",
                "--admin-user-id",
                "user.test.admin",
                "--reason-code",
                "planned_catalog_release",
                "--note",
                "Reviewed release",
            ]
        )
        self.assertIs(stage.handler, licensing_cli._stage_current_catalog)
        self.assertEqual(stage.reason_code, "planned_catalog_release")
        publish = parser.parse_args(
            [
                "catalog",
                "publish-current",
                "--admin-user-id",
                "user.test.admin",
                "--reason-code",
                "planned_catalog_release",
                "--expected-state-digest",
                "ab" * 32,
                "--confirm",
            ]
        )
        self.assertIs(publish.handler, licensing_cli._publish_current_catalog)
        self.assertEqual(publish.expected_state_digest, "ab" * 32)
        self.assertTrue(publish.confirm)

    def test_privacy_commands_separate_review_execution_and_retention(self) -> None:
        parser = _parser()
        listing = parser.parse_args(
            [
                "privacy",
                "list",
                "--admin-user-id",
                "user.test.admin",
                "--status",
                "pending",
                "--limit",
                "25",
            ]
        )
        self.assertIs(listing.handler, licensing_cli._list_privacy_requests)
        self.assertEqual(listing.limit, 25)
        preview = parser.parse_args(
            [
                "privacy",
                "erase-account",
                "--request-id",
                "privacy_request.test.one",
                "--admin-user-id",
                "user.test.admin",
                "--reason-code",
                "customer_request",
            ]
        )
        self.assertIs(preview.handler, licensing_cli._erase_privacy_account)
        self.assertFalse(preview.confirm)
        self.assertIsNone(preview.expected_state_digest)
        execute = parser.parse_args(
            [
                "privacy",
                "erase-account",
                "--request-id",
                "privacy_request.test.one",
                "--admin-user-id",
                "user.test.admin",
                "--reason-code",
                "customer_request",
                "--expected-state-digest",
                "ab" * 32,
                "--confirm",
            ]
        )
        self.assertTrue(execute.confirm)
        self.assertEqual(execute.expected_state_digest, "ab" * 32)
        purge = parser.parse_args(
            [
                "privacy",
                "purge-request-records",
                "--retention-days",
                "400",
                "--maximum-requests",
                "100",
            ]
        )
        self.assertIs(
            purge.handler,
            licensing_cli._purge_privacy_request_records,
        )
        self.assertEqual(purge.retention_days, 400)
        self.assertEqual(purge.maximum_requests, 100)

    def test_serial_export_is_exclusive_owner_only_and_complete(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            path = Path(directory) / "serials.json"
            document = {
                "schema": "apolon.licensing.serial-batch-export",
                "schemaVersion": 1,
                "batchId": "batch.test",
                "skuId": "edition.personal",
                "quantity": 1,
                "serials": ["MSA1-TEST-SERIAL"],
            }
            _write_owner_only_serial_export(path, document)
            self.assertEqual(json.loads(path.read_text(encoding="utf-8")), document)
            if os.name != "nt":
                self.assertEqual(path.stat().st_mode & 0o077, 0)
            with self.assertRaises(FileExistsError):
                _write_owner_only_serial_export(path, document)


if __name__ == "__main__":
    unittest.main()
