from __future__ import annotations

import logging
import os
import tempfile
from pathlib import Path
import hashlib

import pytest

from update import verify_signature, PUBLIC_KEY_N, PUBLIC_KEY_E, TEST_PUBLIC_KEY_N, TEST_PUBLIC_KEY_E, TEST_PRIVATE_KEY_D
from identity import encrypt_token, decrypt_token, ensure_dirs
from logger import RedactingFilter
from config import Config


def test_rsa_signature_verification():
    # Test valid signature verification using the TEST-ONLY keypair (see
    # update.py: TEST_PUBLIC_KEY_N / TEST_PRIVATE_KEY_D). This keypair is
    # unrelated to PUBLIC_KEY_N, the real production key -- we must never
    # have that private key available to sign with in tests.
    message = b"test update release content"
    sha256_hash = hashlib.sha256(message).digest()

    # Sign message
    asn1_prefix = b"\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20"
    padded = b"\x00\x01" + b"\xff" * 202 + b"\x00" + asn1_prefix + sha256_hash
    m = int.from_bytes(padded, "big")
    sig_int = pow(m, TEST_PRIVATE_KEY_D, TEST_PUBLIC_KEY_N)
    valid_sig_hex = sig_int.to_bytes(256, "big").hex()

    assert verify_signature(message, valid_sig_hex, n=TEST_PUBLIC_KEY_N, e=TEST_PUBLIC_KEY_E) is True

    # Test invalid signature (modified message)
    assert verify_signature(b"modified message", valid_sig_hex, n=TEST_PUBLIC_KEY_N, e=TEST_PUBLIC_KEY_E) is False

    # Test invalid signature (random signature)
    assert verify_signature(message, "ab" * 256, n=TEST_PUBLIC_KEY_N, e=TEST_PUBLIC_KEY_E) is False

    # Test malformed signature length
    assert verify_signature(message, "1234") is False
    assert verify_signature(message, "") is False

    # A signature valid under the TEST key must NOT verify against the real
    # production public key -- that separation is the entire point of having
    # two distinct keypairs.
    assert verify_signature(message, valid_sig_hex) is False
    assert TEST_PUBLIC_KEY_N != PUBLIC_KEY_N


def test_token_encryption_decryption():
    fingerprint = "fp_test_machine_fingerprint_12345"
    token = "wp_agent_secret_token_value_xyz"
    
    # Encrypt
    encrypted = encrypt_token(token, fingerprint)
    assert encrypted.startswith("wp_enc:")
    assert encrypted != token
    
    # Decrypt
    decrypted = decrypt_token(encrypted, fingerprint)
    assert decrypted == token
    
    # Decrypt with wrong fingerprint should fail or return wrong value
    with pytest.raises(Exception):
        # Since standard CTR mode doesn't sign by default, decrypting with wrong key
        # will yield garbage, not the original token. Let's verify it does not match.
        wrong_decrypted = decrypt_token(encrypted, "wrong_fingerprint")
        assert wrong_decrypted != token
        
    # Test backward compatibility (plaintext)
    plaintext_token = "wp_agent_legacy_plain_token"
    assert decrypt_token(plaintext_token, fingerprint) == plaintext_token


def test_log_redaction():
    # Create RedactingFilter
    f = RedactingFilter()
    
    # Helper to check record formatting
    class MockRecord(logging.LogRecord):
        def __init__(self, msg, args=()):
            super().__init__("test_logger", logging.INFO, "path/to/file", 10, msg, args, None)
            
    # Test token in message
    rec = MockRecord("Connecting with token: wp_agent_123456789abc")
    assert f.filter(rec) is True
    assert "***" in rec.msg
    assert "wp_agent_123456789abc" not in rec.msg
    
    # Test token in args
    rec = MockRecord("Token is %s", ("wp_agent_123",))
    assert f.filter(rec) is True
    assert rec.args[0] == "wp_***"
    
    # Test password in message
    rec = MockRecord("Failed with password=secret_pass")
    assert f.filter(rec) is True
    assert "password=***" in rec.msg
    
    # Test bearer token in message
    rec = MockRecord("Authorization: Bearer my_jwt_token_here")
    assert f.filter(rec) is True
    assert "my_jwt_token_here" not in rec.msg
    assert "***" in rec.msg
    
    # Test install token in dictionary args
    rec = MockRecord("Register body: %(install_token)s", ({"install_token": "wp_install_123"},))
    assert f.filter(rec) is True
    assert rec.args["install_token"] == "wp_***"


def test_least_privilege_permissions():
    with tempfile.TemporaryDirectory() as tmp:
        base = Path(tmp)
        cfg = Config(
            api_url="https://api.wolfpanel.net",
            release_channel="stable",
            release_base="https://downloads.wolfpanel.net/agent",
            server_id="123",
            agent_version="0.1.0",
            heartbeat_interval=60,
            api_mock=True,
            connect_url="",
            mysql_user="root",
            mysql_password="",
            mysql_socket="",
            update_timeout=60,
            backup_retention=5,
            update_retention=3,
            download_timeout=30,
            etc_dir=base / "etc",
            opt_dir=base / "opt",
            var_dir=base / "var",
            log_dir=base / "log",
        )
        
        # Test directory creations set permissions
        ensure_dirs(cfg)
        
        # Windows doesn't support chmod permissions the same way, but on Unix:
        if os.name != "nt":
            assert (base / "etc").stat().st_mode & 0o777 == 0o700
            assert (base / "var").stat().st_mode & 0o777 == 0o700
            assert (base / "log").stat().st_mode & 0o777 == 0o700
            assert (base / "etc" / "secrets").stat().st_mode & 0o777 == 0o700
