# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import annotations

import base64
import datetime
import os
import typing as t

from ansible_collections.community.crypto.plugins.module_utils._acme.backends import (
    CertificateInformation,
    CryptoBackend,
)
from ansible_collections.community.crypto.plugins.module_utils._acme.errors import (
    BackendException,
)

from ..test__time import TIMEZONES, cartesian_product

if t.TYPE_CHECKING:
    from ansible_collections.community.crypto.plugins.module_utils._acme.backends import (  # pragma: no cover
        Criterium,
    )

    class DatetimeKwarg(t.TypedDict):  # pragma: no cover
        year: int
        month: int
        day: int
        hour: t.NotRequired[int]
        minute: t.NotRequired[int]
        second: t.NotRequired[int]
        microsecond: t.NotRequired[int]
        tzinfo: t.NotRequired[datetime.timezone | None]


def load_fixture(name: str) -> str:
    with open(
        os.path.join(os.path.dirname(__file__), "fixtures", name), encoding="utf-8"
    ) as f:
        return f.read()


TEST_PEM_DERS: list[tuple[str, bytes]] = [
    (
        load_fixture("privatekey_1.pem"),
        base64.b64decode(
            "MHcCAQEEIDWajU0PyhYKeulfy/luNtkAve7DkwQ01bXJ97zbxB66oAo"
            "GCCqGSM49AwEHoUQDQgAEAJz0yAAXAwEmOhTRkjXxwgedbWO6gobYM3"
            "lWszrS68G8QSzhXR6AmQ3IzZDimnTTXO7XhVylDT8SLzE44/Epmw=="
        ),
    )
]


TEST_KEYS: list[tuple[str, dict[str, t.Any], str]] = [
    (
        load_fixture("privatekey_1.pem"),
        {
            "alg": "ES256",
            "hash": "sha256",
            "jwk": {
                "crv": "P-256",
                "kty": "EC",
                "x": "AJz0yAAXAwEmOhTRkjXxwgedbWO6gobYM3lWszrS68E",
                "y": "vEEs4V0egJkNyM2Q4pp001zu14VcpQ0_Ei8xOOPxKZs",
            },
            "point_size": 32,
            "type": "ec",
        },
        load_fixture("privatekey_1.txt"),
    )
]


TEST_CSRS: list[tuple[str, set[tuple[str, str]], str]] = [
    (
        load_fixture("csr_1.pem"),
        set([("dns", "ansible.com"), ("dns", "example.com"), ("dns", "example.org")]),
        load_fixture("csr_1.txt"),
    ),
    (
        load_fixture("csr_2.pem"),
        set(
            [
                ("dns", "ansible.com"),
                ("ip", "127.0.0.1"),
                ("ip", "::1"),
                ("ip", "2001:d88:ac10:fe01::"),
                ("ip", "2001:1234:5678:abcd:9876:5432:10fe:dcba"),
            ]
        ),
        load_fixture("csr_2.txt"),
    ),
]


TEST_CERT = load_fixture("cert_1.pem")
TEST_CERT_2 = load_fixture("cert_2.pem")


TEST_CERT_OPENSSL_OUTPUT = load_fixture("cert_1.txt")  # OpenSSL 3.3.0 output
TEST_CERT_OPENSSL_OUTPUT_2 = load_fixture("cert_2.txt")  # OpenSSL 3.3.0 output
TEST_CERT_OPENSSL_OUTPUT_2B = load_fixture("cert_2-b.txt")  # OpenSSL 1.1.1f output


TEST_CERT_DAYS: list[tuple[datetime.timedelta, datetime.datetime, int]] = (
    cartesian_product(
        TIMEZONES,
        [
            (datetime.datetime(2018, 11, 15, 1, 2, 3), 11),
            (datetime.datetime(2018, 11, 25, 15, 20, 0), 1),
            (datetime.datetime(2018, 11, 25, 15, 30, 0), 0),
        ],
    )
)


TEST_CERT_INFO_1 = CertificateInformation(
    not_valid_after=datetime.datetime(2018, 11, 26, 15, 28, 24),
    not_valid_before=datetime.datetime(2018, 11, 25, 15, 28, 23),
    serial_number=1,
    subject_key_identifier=b"\x98\xd2\xfd\x3c\xcc\xcd\x69\x45\xfb\xe2\x8c\x30\x2c\x54\x62\x18\x34\xb7\x07\x73",
    authority_key_identifier=None,
)


TEST_CERT_INFO_2 = CertificateInformation(
    not_valid_before=datetime.datetime(2024, 5, 4, 20, 42, 21),
    not_valid_after=datetime.datetime(2029, 5, 4, 20, 42, 20),
    serial_number=4218235397573492796,
    subject_key_identifier=b"\x17\xe5\x83\x22\x14\xef\x74\xd3\xbe\x7e\x30\x76\x56\x1f\x51\x74\x65\x1f\xe9\xf0",
    authority_key_identifier=b"\x13\xc3\x4c\x3e\x59\x45\xdd\xe3\x63\x51\xa3\x46\x80\xc4\x08\xc7\x14\xc0\x64\x4e",
)


TEST_CERT_INFO: list[tuple[str, CertificateInformation, str]] = [
    (TEST_CERT, TEST_CERT_INFO_1, TEST_CERT_OPENSSL_OUTPUT),
    (TEST_CERT_2, TEST_CERT_INFO_2, TEST_CERT_OPENSSL_OUTPUT_2),
    (TEST_CERT_2, TEST_CERT_INFO_2, TEST_CERT_OPENSSL_OUTPUT_2B),
]


TEST_PARSE_ACME_TIMESTAMP: list[tuple[datetime.timedelta, str, DatetimeKwarg]] = (
    cartesian_product(
        TIMEZONES,
        [
            (
                "2024-01-01T00:11:22Z",
                {
                    "year": 2024,
                    "month": 1,
                    "day": 1,
                    "hour": 0,
                    "minute": 11,
                    "second": 22,
                },
            ),
            (
                "2024-01-01T00:11:22.123Z",
                {
                    "year": 2024,
                    "month": 1,
                    "day": 1,
                    "hour": 0,
                    "minute": 11,
                    "second": 22,
                    "microsecond": 123000,
                },
            ),
            (
                "2024-04-17T06:54:13.333333334Z",
                {
                    "year": 2024,
                    "month": 4,
                    "day": 17,
                    "hour": 6,
                    "minute": 54,
                    "second": 13,
                    "microsecond": 333333,
                },
            ),
            (
                "2024-01-01T00:11:22+0100",
                {
                    "year": 2023,
                    "month": 12,
                    "day": 31,
                    "hour": 23,
                    "minute": 11,
                    "second": 22,
                },
            ),
            (
                "2024-01-01T00:11:22.123+0100",
                {
                    "year": 2023,
                    "month": 12,
                    "day": 31,
                    "hour": 23,
                    "minute": 11,
                    "second": 22,
                    "microsecond": 123000,
                },
            ),
        ],
    )
)


TEST_INTERPOLATE_TIMESTAMP: list[
    tuple[datetime.timedelta, DatetimeKwarg, DatetimeKwarg, float, DatetimeKwarg]
] = cartesian_product(
    TIMEZONES,
    [
        (
            {"year": 2024, "month": 1, "day": 1, "hour": 0, "minute": 0, "second": 0},
            {"year": 2024, "month": 1, "day": 1, "hour": 1, "minute": 0, "second": 0},
            0.0,
            {"year": 2024, "month": 1, "day": 1, "hour": 0, "minute": 0, "second": 0},
        ),
        (
            {"year": 2024, "month": 1, "day": 1, "hour": 0, "minute": 0, "second": 0},
            {"year": 2024, "month": 1, "day": 1, "hour": 1, "minute": 0, "second": 0},
            0.5,
            {"year": 2024, "month": 1, "day": 1, "hour": 0, "minute": 30, "second": 0},
        ),
        (
            {"year": 2024, "month": 1, "day": 1, "hour": 0, "minute": 0, "second": 0},
            {"year": 2024, "month": 1, "day": 1, "hour": 1, "minute": 0, "second": 0},
            1.0,
            {"year": 2024, "month": 1, "day": 1, "hour": 1, "minute": 0, "second": 0},
        ),
    ],
)


class FakeBackend(CryptoBackend):
    def parse_key(
        self,
        *,
        key_file: str | os.PathLike | None = None,
        key_content: str | None = None,
        passphrase: str | None = None,
    ) -> t.NoReturn:
        raise BackendException("Not implemented in fake backend")  # pragma: no cover

    def sign(
        self, *, payload64: str, protected64: str, key_data: dict[str, t.Any] | None
    ) -> t.NoReturn:
        raise BackendException("Not implemented in fake backend")  # pragma: no cover

    def create_mac_key(self, *, alg: str, key: str) -> t.NoReturn:
        raise BackendException("Not implemented in fake backend")  # pragma: no cover

    def get_ordered_csr_identifiers(
        self,
        *,
        csr_filename: str | os.PathLike | None = None,
        csr_content: str | bytes | None = None,
    ) -> t.NoReturn:
        raise BackendException("Not implemented in fake backend")  # pragma: no cover

    def get_csr_identifiers(
        self,
        *,
        csr_filename: str | os.PathLike | None = None,
        csr_content: str | bytes | None = None,
    ) -> t.NoReturn:
        raise BackendException("Not implemented in fake backend")  # pragma: no cover

    def get_cert_days(
        self,
        *,
        cert_filename: str | os.PathLike | None = None,
        cert_content: str | bytes | None = None,
        now: datetime.datetime | None = None,
    ) -> t.NoReturn:
        raise BackendException("Not implemented in fake backend")  # pragma: no cover

    def create_chain_matcher(self, *, criterium: Criterium) -> t.NoReturn:
        raise BackendException("Not implemented in fake backend")  # pragma: no cover

    def get_cert_information(
        self,
        *,
        cert_filename: str | os.PathLike | None = None,
        cert_content: str | bytes | None = None,
    ) -> t.NoReturn:
        raise BackendException("Not implemented in fake backend")  # pragma: no cover
