import base64
import hashlib
import hmac
import typing as t
from datetime import datetime, timedelta, timezone
from math import floor

import pytest

from svix.webhooks import Webhook, WebhookVerificationError

defaultMsgID = "msg_p5jXN8AQM9LWM0D4loKWxJek"
defaultPayload = '{"test": 2432232314}'
defaultSecret = "MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"

tolerance = timedelta(minutes=5)


def hmac_data(key: bytes, data: bytes) -> bytes:
    return hmac.new(key, data, hashlib.sha256).digest()


class PayloadForTesting:
    id: str
    timestamp: int
    payload: str
    secret: str
    signature: str
    header: t.Dict[str, str]

    def __init__(self, timestamp: datetime = datetime.now(tz=timezone.utc)):
        ts = floor(timestamp.timestamp())
        to_sign = f"{defaultMsgID}.{ts}.{defaultPayload}".encode()
        signature = base64.b64encode(
            hmac_data(base64.b64decode(defaultSecret), to_sign)
        ).decode("utf-8")

        self.id = defaultMsgID
        self.timestamp = ts
        self.payload = defaultPayload
        self.secret = defaultSecret
        self.signature = signature
        self.header = {
            "svix-id": defaultMsgID,
            "svix-signature": "v1," + signature,
            "svix-timestamp": str(self.timestamp),
        }


def test_missing_id_raises_error():
    testPayload = PayloadForTesting()
    del testPayload.header["svix-id"]

    wh = Webhook(testPayload.secret)

    with pytest.raises(WebhookVerificationError):
        wh.verify(testPayload.payload, testPayload.header)


def test_timestamp_raises_error():
    testPayload = PayloadForTesting()
    del testPayload.header["svix-timestamp"]

    wh = Webhook(testPayload.secret)

    with pytest.raises(WebhookVerificationError):
        wh.verify(testPayload.payload, testPayload.header)


def test_invalid_timestamp_raises_error():
    testPayload = PayloadForTesting()
    testPayload.header["svix-timestamp"] = "hello"

    wh = Webhook(testPayload.secret)

    with pytest.raises(WebhookVerificationError):
        wh.verify(testPayload.payload, testPayload.header)


def test_missing_signature_raises_error():
    testPayload = PayloadForTesting()
    del testPayload.header["svix-signature"]

    wh = Webhook(testPayload.secret)

    with pytest.raises(WebhookVerificationError):
        wh.verify(testPayload.payload, testPayload.header)


def test_invalid_signature_raises_error():
    testPayload = PayloadForTesting()
    testPayload.header["svix-signature"] = (
        "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OA="
    )

    wh = Webhook(testPayload.secret)

    with pytest.raises(WebhookVerificationError):
        wh.verify(testPayload.payload, testPayload.header)


def test_valid_signature_is_valid():
    testPayload = PayloadForTesting()

    wh = Webhook(testPayload.secret)
    wh.verify(testPayload.payload, testPayload.header)


def test_valid_unbranded_signature_is_valid():
    testPayload = PayloadForTesting()
    unbrandedHeaders = {
        "webhook-id": testPayload.header["svix-id"],
        "webhook-signature": testPayload.header["svix-signature"],
        "webhook-timestamp": testPayload.header["svix-timestamp"],
    }
    testPayload.header = unbrandedHeaders

    wh = Webhook(testPayload.secret)
    wh.verify(testPayload.payload, testPayload.header)


def test_old_timestamp_fails():
    testPayload = PayloadForTesting(
        datetime.now(tz=timezone.utc) - tolerance - timedelta(seconds=1)
    )

    wh = Webhook(testPayload.secret)

    with pytest.raises(WebhookVerificationError):
        wh.verify(testPayload.payload, testPayload.header)


def test_new_timestamp_fails():
    testPayload = PayloadForTesting(
        datetime.now(tz=timezone.utc) + tolerance + timedelta(seconds=1)
    )

    wh = Webhook(testPayload.secret)

    with pytest.raises(WebhookVerificationError):
        wh.verify(testPayload.payload, testPayload.header)


def test_multi_sig_payload_is_valid():
    testPayload = PayloadForTesting()
    sigs = [
        "v1,Ceo5qEr07ixe2NLpvHk3FH9bwy/WavXrAFQ/9tdO6mc=",
        "v2,Ceo5qEr07ixe2NLpvHk3FH9bwy/WavXrAFQ/9tdO6mc=",
        testPayload.header["svix-signature"],  # valid signature
        "v1,Ceo5qEr07ixe2NLpvHk3FH9bwy/WavXrAFQ/9tdO6mc=",
    ]
    testPayload.header["svix-signature"] = " ".join(sigs)

    wh = Webhook(testPayload.secret)
    wh.verify(testPayload.payload, testPayload.header)


def test_signature_verification_with_and_without_prefix():
    testPayload = PayloadForTesting()

    wh = Webhook(testPayload.secret)
    wh.verify(testPayload.payload, testPayload.header)

    wh = Webhook("whsec_" + testPayload.secret)
    wh.verify(testPayload.payload, testPayload.header)


def test_sign_function():
    key = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
    msg_id = "msg_p5jXN8AQM9LWM0D4loKWxJek"
    timestamp = datetime.fromtimestamp(1614265330, tz=timezone.utc)
    payload = '{"test": 2432232314}'
    expected = "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE="

    wh = Webhook(key)
    signature = wh.sign(msg_id=msg_id, timestamp=timestamp, data=payload)
    assert signature == expected
