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

import edge_inputs


def configured(**changes):
    return {"public_health_url": "https://app.pages.dev/api/healthz",
            "public_base_url": "https://app.pages.dev/api", "expected_build_sha": "a" * 40,
            "ingress_required": "true", "ingress_token": "synthetic-token-" + "a" * 43, **changes}


class Inputs(unittest.TestCase):
    def test_edge_and_legacy_configurations(self):
        self.assertEqual(edge_inputs.mode(edge_inputs.validate(configured())), "edge")
        with tempfile.TemporaryDirectory() as directory:
            value = edge_inputs.load(Path(directory) / "absent")
        self.assertEqual(edge_inputs.mode(value), "legacy")
        self.assertEqual(edge_inputs.mode(configured(ingress_required="false")), "verify")

    def test_invalid_inputs_refuse_without_credential_output(self):
        cases = [{"ingress_token": ""}, {"ingress_token": 'private\"; return 200;'},
                 {"ingress_required": "yes"}, {"expected_build_sha": "A" * 40},
                 {"public_health_url": "http://app.pages.dev/api/healthz"},
                 {"public_health_url": "https://other.pages.dev/api/healthz"},
                 {"public_base_url": "https://app.pages.dev/api/$TOKEN"},
                 {"public_base_url": ""}, {"expected_build_sha": ""},
                 {"public_health_url": "https://user:private@app.pages.dev/api/healthz"}]
        for changes in cases:
            with self.subTest(changes=changes), self.assertRaises(ValueError):
                edge_inputs.validate(configured(**changes))
        env = {"BACKEND_" + key.upper(): value for key, value in configured(ingress_token="private").items()}
        result = subprocess.run([sys.executable, str(Path(edge_inputs.__file__)), "check"],
                                env={**os.environ, **env}, capture_output=True, text=True)
        self.assertNotEqual(result.returncode, 0)
        self.assertNotIn("private", result.stdout + result.stderr)

    def test_runtime_input_is_private_and_separate_from_secret_dotenv(self):
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            config = root / edge_inputs.CONFIG_NAME
            config.write_text(json.dumps(configured()))
            secrets = root / ".runtime.env"
            secrets.write_text("PASSWORD=synthetic-private\n")
            edge_inputs.runtime(config)
            output = root / ".backend-public.env"
            self.assertEqual(output.read_text(), 'BACKEND_PUBLIC_BASE_URL="https://app.pages.dev/api"\n')
            self.assertEqual(output.stat().st_mode & 0o777, 0o600)
            self.assertEqual(secrets.read_text(), "PASSWORD=synthetic-private\n")
            edge_inputs.runtime(config)
            output.unlink()
            output.symlink_to(secrets)
            with self.assertRaises(ValueError):
                edge_inputs.runtime(config)
            self.assertEqual(secrets.read_text(), "PASSWORD=synthetic-private\n")

    def test_private_input_receive_is_atomic_and_refuses_symlinks(self):
        with tempfile.TemporaryDirectory() as directory:
            path = Path(directory) / ".backend-edge.json"
            command = [sys.executable, str(Path(edge_inputs.__file__)), "receive", str(path)]
            value = json.dumps(configured())
            result = subprocess.run(command, input=value, capture_output=True, text=True)
            self.assertEqual(result.returncode, 0, result.stderr)
            self.assertEqual(result.stdout, "")
            self.assertEqual(path.stat().st_mode & 0o777, 0o600)
            invalid = subprocess.run(command, input="broken", capture_output=True, text=True)
            self.assertNotEqual(invalid.returncode, 0)
            self.assertEqual(json.loads(path.read_text()), configured())
            other = Path(directory) / "preserved"
            path.rename(other)
            path.symlink_to(other)
            refused = subprocess.run(command, input=value, capture_output=True, text=True)
            self.assertNotEqual(refused.returncode, 0)
            self.assertEqual(json.loads(other.read_text()), configured())
