import json
import os
import subprocess
import tempfile
import textwrap
import unittest
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parents[3]
ENTRYPOINT = REPO_ROOT / "documentdb-local" / "scripts" / "emulator_entrypoint.sh"


class EmulatorEntrypointTests(unittest.TestCase):
    def setUp(self):
        self.temp_dir = tempfile.TemporaryDirectory()
        self.root = Path(self.temp_dir.name)
        self.gateway_home = self.root / "gateway"
        self.gateway_scripts = self.gateway_home / "scripts"
        self.gateway_config_dir = self.gateway_home / "pg_documentdb_gw"
        self.gateway_target_dir = self.gateway_config_dir / "target"
        self.sample_data_dir = self.gateway_home / "sample-data"
        self.bin_dir = self.root / "bin"
        self.logs_dir = self.root / "logs"
        self.data_dir = self.root / "data"

        self.gateway_scripts.mkdir(parents=True)
        self.gateway_target_dir.mkdir(parents=True)
        self.sample_data_dir.mkdir(parents=True)
        self.bin_dir.mkdir(parents=True)
        self.logs_dir.mkdir(parents=True)
        self.data_dir.mkdir(parents=True)

        (self.gateway_config_dir / "SetupConfiguration.json").write_text(
            json.dumps(
                {
                    "NodeHostName": "localhost",
                    "BlockedRolePrefixes": ["documentdb"],
                    "PostgresPort": 9712,
                    "GatewayListenPort": 10260,
                    "HostConfigurationWatchIntervalMs": 1000,
                    "CertificateOptions": {"CertType": "PemAutoGenerated"},
                    "UseLocalHost": False,
                }
            ),
            encoding="utf-8",
        )

        self._write_exec(
            self.bin_dir / "sudo",
            """#!/bin/sh
if [ "$1" = "chown" ]; then
  exit 0
fi
exec "$@"
""",
        )
        self._write_exec(self.bin_dir / "nc", "#!/bin/sh\nexit 0\n")
        self._write_exec(self.bin_dir / "tail", "#!/bin/sh\nexit 0\n")
        self._write_exec(
            self.bin_dir / "jq",
            """#!/usr/bin/env python3
import json
import sys
args = sys.argv[1:]
vars = {}
while args and args[0] in ('--arg', '--argjson'):
    flag, name, value, *args = args
    vars[name] = json.loads(value) if flag == '--argjson' else value
expr, file_path = args
with open(file_path, 'r', encoding='utf-8') as f:
    data = json.load(f)
expr = expr.strip()
if expr.startswith('.GatewayListenPort = '):
    data['GatewayListenPort'] = int(expr.split('=', 1)[1].strip())
elif expr.startswith('.PostgresPort = '):
    data['PostgresPort'] = int(expr.split('=', 1)[1].strip())
elif expr.startswith('.TlsMode = '):
    data['TlsMode'] = vars.get('tlsMode', expr.split('=', 1)[1].strip().strip('"'))
elif expr.startswith('.EnforceTls = '):
    data['EnforceTls'] = vars['enforceTls']
elif expr.startswith('.CertificateOptions = '):
    data['CertificateOptions'] = {
        'CertType': 'PemFile',
        'FilePath': vars['certPath'],
        'KeyFilePath': vars['keyFilePath'],
    }
else:
    raise SystemExit(f'Unsupported jq expression: {expr}')
json.dump(data, sys.stdout)
""",
        )

        self._write_exec(
            self.gateway_scripts / "build_and_start_gateway.sh",
            "#!/bin/sh\necho gateway-stub-started\n",
        )
        self._write_exec(
            self.gateway_scripts / "start_oss_server.sh",
            "#!/bin/sh\necho oss-server-stub-started\n",
        )
        self._write_exec(
            self.gateway_scripts / "init_documentdb_data.sh",
            "#!/bin/sh\necho init-script-stub-started\n",
        )

    def tearDown(self):
        self.temp_dir.cleanup()

    def _write_exec(self, path: Path, content: str):
        path.write_text(textwrap.dedent(content), encoding="utf-8")
        path.chmod(0o755)

    def _run_entrypoint(self, *args, extra_env=None):
        env = os.environ.copy()
        env.update(
            {
                "PATH": f"{self.bin_dir}:{env['PATH']}",
                "GATEWAY_HOME": str(self.gateway_home),
                "DOCUMENTDB_LOG_DIR": str(self.logs_dir),
                "SYSTEM_POSTGRES_LOG": str(self.root / "postgres-system.log"),
                "START_POSTGRESQL": "false",
                "SKIP_INIT_DATA": "true",
                "CREATE_USER": "false",
                "PASSWORD": "mypassword",
                "DATA_PATH": str(self.data_dir),
                "USERNAME": "default_user",
                "OWNER": "documentdb",
            }
        )
        if extra_env:
            env.update(extra_env)
        return subprocess.run(
            ["bash", str(ENTRYPOINT), *args],
            cwd=REPO_ROOT,
            env=env,
            text=True,
            capture_output=True,
            timeout=30,
        )

    def _read_config(self):
        with (self.gateway_target_dir / "SetupConfiguration_temp.json").open(
            "r", encoding="utf-8"
        ) as file:
            return json.load(file)

    def test_default_mode_sets_allowTLS_in_config(self):
        result = self._run_entrypoint("--password", "mypassword")
        self.assertEqual(result.returncode, 0, msg=result.stdout + result.stderr)
        config = self._read_config()
        self.assertEqual(config["TlsMode"], "allowTLS")
        self.assertEqual(config["EnforceTls"], False)
        self.assertEqual(config["GatewayListenPort"], 10260)
        self.assertEqual(config["PostgresPort"], 9712)
        self.assertNotIn("does not turn TLS off", result.stdout + result.stderr)

    def test_tlsMode_requireTLS_flag_sets_requireTLS_in_config(self):
        result = self._run_entrypoint(
            "--password", "mypassword", "--tlsMode", "requireTLS"
        )
        self.assertEqual(result.returncode, 0, msg=result.stdout + result.stderr)
        config = self._read_config()
        self.assertEqual(config["TlsMode"], "requireTLS")
        self.assertEqual(config["EnforceTls"], True)

    def test_tlsMode_disabled_flag_sets_disabled_in_config(self):
        result = self._run_entrypoint(
            "--password", "mypassword", "--tlsMode", "disabled"
        )
        self.assertEqual(result.returncode, 0, msg=result.stdout + result.stderr)
        config = self._read_config()
        self.assertEqual(config["TlsMode"], "disabled")
        self.assertEqual(config["EnforceTls"], False)
        self.assertIn("does not turn TLS off", result.stdout + result.stderr)

    def test_tlsMode_env_var_sets_mode_in_config(self):
        result = self._run_entrypoint(
            "--password",
            "mypassword",
            extra_env={"TLS_MODE": "requireTLS"},
        )
        self.assertEqual(result.returncode, 0, msg=result.stdout + result.stderr)
        config = self._read_config()
        self.assertEqual(config["TlsMode"], "requireTLS")
        self.assertEqual(config["EnforceTls"], True)

    def test_tlsMode_env_var_allowTLS_does_not_enforce_tls(self):
        result = self._run_entrypoint(
            "--password",
            "mypassword",
            extra_env={"TLS_MODE": "allowTLS"},
        )
        self.assertEqual(result.returncode, 0, msg=result.stdout + result.stderr)
        config = self._read_config()
        self.assertEqual(config["TlsMode"], "allowTLS")
        self.assertEqual(config["EnforceTls"], False)

    def test_system_postgres_log_defaults_to_runtime_pg_version(self):
        result = self._run_entrypoint(
            "--password",
            "mypassword",
            extra_env={"PG_VERSION_USED": "18", "SYSTEM_POSTGRES_LOG": ""},
        )
        self.assertEqual(result.returncode, 0, msg=result.stdout + result.stderr)
        self.assertIn(
            "System PostgreSQL logs (/var/log/postgresql/postgresql-18-main.log)",
            result.stdout,
        )

    def test_invalid_tlsMode_value_fails_fast(self):
        result = self._run_entrypoint(
            "--password",
            "mypassword",
            extra_env={"TLS_MODE": "maybe"},
        )
        self.assertNotEqual(result.returncode, 0)
        self.assertIn(
            "Invalid tlsMode value 'maybe', must be one of: disabled, allowTLS, requireTLS",
            result.stdout + result.stderr,
        )

    def test_enforce_tls_write_failure_aborts(self):
        failing_jq = """#!/usr/bin/env python3
import json
import sys
args = sys.argv[1:]
vars = {}
while args and args[0] in ('--arg', '--argjson'):
    flag, name, value, *args = args
    vars[name] = json.loads(value) if flag == '--argjson' else value
expr, file_path = args
expr = expr.strip()
if expr.startswith('.EnforceTls = '):
    sys.stderr.write('jq: simulated failure\\n')
    sys.exit(1)
with open(file_path, 'r', encoding='utf-8') as f:
    data = json.load(f)
if expr.startswith('.GatewayListenPort = '):
    data['GatewayListenPort'] = int(expr.split('=', 1)[1].strip())
elif expr.startswith('.PostgresPort = '):
    data['PostgresPort'] = int(expr.split('=', 1)[1].strip())
elif expr.startswith('.TlsMode = '):
    data['TlsMode'] = vars.get('tlsMode', expr.split('=', 1)[1].strip().strip('"'))
else:
    raise SystemExit(f'Unsupported jq expression: {expr}')
json.dump(data, sys.stdout)
"""
        self._write_exec(self.bin_dir / "jq", failing_jq)
        result = self._run_entrypoint("--password", "mypassword")
        self.assertNotEqual(result.returncode, 0)
        self.assertIn(
            "failed to write EnforceTls to the gateway configuration file",
            result.stdout + result.stderr,
        )

    def test_pem_certificate_and_key_are_written_to_config(self):
        result = self._run_entrypoint(
            "--password",
            "mypassword",
            "--cert-path",
            "/tmp/documentdb-local.pem",
            "--key-file",
            "/tmp/documentdb-local.key",
        )
        self.assertEqual(result.returncode, 0, msg=result.stdout + result.stderr)
        config = self._read_config()
        self.assertEqual(
            config["CertificateOptions"],
            {
                "CertType": "PemFile",
                "FilePath": "/tmp/documentdb-local.pem",
                "KeyFilePath": "/tmp/documentdb-local.key",
            },
        )

    def test_pfx_certificate_without_key_file_is_rejected(self):
        result = self._run_entrypoint(
            "--password",
            "mypassword",
            "--cert-path",
            "/tmp/documentdb-local.pfx",
        )
        self.assertNotEqual(result.returncode, 0)
        self.assertIn(
            "Error: Both CERT_PATH and KEY_FILE must be set together, or neither should be set.",
            result.stdout + result.stderr,
        )

    def test_key_file_without_certificate_is_rejected(self):
        result = self._run_entrypoint(
            "--password",
            "mypassword",
            "--key-file",
            "/tmp/documentdb-local.key",
        )
        self.assertNotEqual(result.returncode, 0)
        self.assertIn(
            "Error: Both CERT_PATH and KEY_FILE must be set together, or neither should be set.",
            result.stdout + result.stderr,
        )

    def test_help_describes_tlsMode_and_pem_certificate_requirements(self):
        result = self._run_entrypoint("--help")
        self.assertEqual(result.returncode, 0, msg=result.stdout + result.stderr)
        self.assertIn("--tlsMode", result.stdout)
        self.assertIn(
            "By default, the gateway accepts both plain and TLS connections",
            result.stdout,
        )
        self.assertIn("Specify a path to a PEM certificate", result.stdout)
        self.assertIn(
            "PEM certificates must be provided together with --key-file.",
            result.stdout,
        )
        self.assertNotIn("CERT_SECRET", result.stdout)


if __name__ == "__main__":
    unittest.main()
