import datetime
import os
from collections.abc import Generator
from email.message import Message
from ipaddress import IPv4Interface, IPv4Network
from pathlib import Path
from typing import Any

import fakeredis
import msgspec
import pytest
from temporalio.testing import ActivityEnvironment

from csirtmu.beta.config import AppConfig, Config
from csirtmu.beta.handler_builder import HandlerBuilder
from csirtmu.beta.rt_queue import Queue
from csirtmu.beta_temporal.config import AppConfig as WorkerAppConfig
from csirtmu.beta_temporal.config import Config as WorkerConfig
from csirtmu.beta_temporal.external_services.alpha.structs import CIDR, NetboxAsset, QueryResult, Tenant
from csirtmu.beta_temporal.external_services.netbox.structs import NetboxIPDTO, NetboxPrefixDTO, NetboxSubnetInfoDTO
from csirtmu.beta_temporal.external_services.rt import structs

BASE_DIR = Path(__file__).parent.resolve()


@pytest.fixture(scope="session")
def anyio_backend() -> str:
    return "asyncio"


@pytest.fixture(scope="session")
def monkey_session() -> Generator[pytest.MonkeyPatch, None, None]:
    with pytest.MonkeyPatch.context() as mp:
        yield mp


@pytest.fixture
def temporal_patch_exception() -> type[Any]:
    class TemporalClient:
        def __init__(self) -> None: ...

        async def start_workflow(self, *args: Any, **kwargs: Any) -> None:
            raise Exception("Test exception")

    return TemporalClient


@pytest.fixture(scope="session", autouse=True)
def setup_env() -> None:
    if not os.environ.get("CSIRTMU_ALPHA_CONFIG_PATH"):  # pragma: no cover
        os.environ["CSIRTMU_ALPHA_CONFIG_PATH"] = str(BASE_DIR / "config/config.yaml")  # pragma: no cover


@pytest.fixture(scope="session")
def app_config() -> Config:
    config_path = Path(os.environ.get("CSIRTMU_BETA_CONFIG_PATH", BASE_DIR / "config/config.yaml"))
    return AppConfig.get(config_path)


@pytest.fixture(scope="session")
def worker_config() -> WorkerConfig:
    config_path = Path(os.environ.get("CSIRTMU_BETA_WORKER_CONFIG_PATH", BASE_DIR / "config/worker_config.yaml"))
    return WorkerAppConfig.get(config_path)


@pytest.fixture
def queue_map(temporal_patch_exception: type[Any], app_config: Config) -> dict[str, Queue]:
    return HandlerBuilder(temporal_patch_exception(), fakeredis.FakeRedis()).build(app_config.queue_mapping).queue_map


@pytest.fixture
def mock_transaction() -> str:
    with Path(BASE_DIR / "assets/test_transaction.json").open("r") as f:
        return f.read()


def get_mock_ticket_obj() -> structs.TicketObjStruct:
    user = structs.UserObjStruct(id="", type="", url="")
    owner = structs.UserObjStruct(id="1", type="user", url="url")
    return structs.TicketObjStruct(
        admin_cc=[],
        cc=[],
        created=datetime.datetime.now(datetime.UTC) - datetime.timedelta(minutes=3),
        creator=user,
        custom_fields=[],
        effective_id=structs.EffectiveIdStruct(id="", type="", url=""),
        final_priority_src="",
        initial_priority_src="",
        last_updated=datetime.datetime.now(datetime.UTC) - datetime.timedelta(minutes=1),
        last_updated_by=user,
        priority_src="",
        queue=structs.QueueObjStruct(id="", type="", url=""),
        requestor=[user],
        status="",
        subject="",
        time_estimated_src="",
        time_left_src="",
        time_worked_src="",
        type="",
        id=1,
        owner=owner,
    )


@pytest.fixture
def mock_ticket_obj() -> structs.TicketObjStruct:
    return get_mock_ticket_obj()


@pytest.fixture
def mock_transaction_obj(mock_transaction: str, mock_ticket_obj: structs.TicketObjStruct) -> structs.TransactionStruct:
    transaction = msgspec.json.decode(mock_transaction, type=structs.TransactionStruct)
    transaction.ticket_obj = get_mock_ticket_obj()
    return transaction


@pytest.fixture
def mock_ticket_data(mock_transaction_obj: structs.TransactionStruct) -> structs.TicketData:
    return structs.TicketData(
        transaction=mock_transaction_obj,
        transaction_from_api=structs.TransactionObjectFromAPI(
            type=mock_transaction_obj.type,
            created=mock_transaction_obj.created,
            time_taken_src="",
            id=mock_transaction_obj.id,
            object=mock_transaction_obj.object,
            hyperlinks=[],
            creator=mock_transaction_obj.creator,
        ),
        attachments=[
            structs.AttachmentObjectFromAPI(
                headers="",
                created=mock_transaction_obj.created,
                id=1,
                content_type="",
                creator=mock_transaction_obj.creator,
                transaction_id=mock_transaction_obj.object,
                message_id="",
                hyperlinks=[],
                subject="",
                parent="",
            )
        ],
        body=" ",
        headers=Message(),
    )


@pytest.fixture
def mock_ticket_data_with_ip_body(mock_ticket_data: structs.TicketData) -> structs.TicketData:
    mock_ticket_data.body = Path(BASE_DIR / "assets/ticket_body_with_ips.txt").read_text(encoding="utf-8")
    return mock_ticket_data


@pytest.fixture
def test_activity_env() -> ActivityEnvironment:
    return ActivityEnvironment()


@pytest.fixture
def mock_nats_msg(mock_transaction: str) -> Any:
    class MockData:
        def __init__(self) -> None:
            self.data = mock_transaction

        def decode(self) -> str:
            return self.data

    class MockMessage:
        def __init__(self, subject: str, reply: str, data: MockData) -> None:
            self.subject = subject
            self.reply = reply
            self.data = data

        async def ack(self) -> None:
            return None

    return MockMessage("test subject", "test reply", MockData())


@pytest.fixture
def mock_rt_client() -> Any:
    class MockRtClient:
        def __init__(self) -> None: ...

    return MockRtClient


@pytest.fixture
def mock_subnet_info() -> NetboxSubnetInfoDTO:
    return NetboxSubnetInfoDTO(
        ip_addresses=[
            NetboxIPDTO(
                description="",
                address=IPv4Interface("127.0.0.1/32"),
                tenant=None,
                custom_fields={},
            )
        ],
        prefixes=[
            NetboxPrefixDTO(
                description="",
                tenant=None,
                custom_fields={},
                prefix=IPv4Network("127.0.0.0/16"),
            )
        ],
    )


@pytest.fixture
def mock_alpha_subnet_info() -> dict[str, QueryResult] | None:
    return {
        "147.251.1.1": QueryResult(
            ip_addresses=[
                NetboxAsset(
                    description="",
                    cidr=CIDR(
                        _raw_exploded="147.251.1.1/32",
                    ),
                    tenant=[Tenant(name="")],
                )
            ],
            prefixes=[
                NetboxAsset(
                    description="",
                    cidr=CIDR(
                        _raw_exploded="147.251.0.0/16",
                    ),
                    tenant=[Tenant(name="")],
                )
            ],
        ),
        "100.10.10.1": QueryResult(
            ip_addresses=[
                NetboxAsset(
                    description="",
                    cidr=CIDR(
                        _raw_exploded="100.10.10.1/32",
                    ),
                    tenant=[Tenant(name="")],
                )
            ],
            prefixes=[
                NetboxAsset(
                    description="",
                    cidr=CIDR(
                        _raw_exploded="100.10.0.0/16",
                    ),
                    tenant=[Tenant(name="")],
                )
            ],
        ),
    }


@pytest.fixture
def mock_api_transaction(mock_transaction_obj: structs.TransactionStruct) -> structs.TransactionObjectFromAPI:
    return structs.TransactionObjectFromAPI(
        type=mock_transaction_obj.type,
        created=mock_transaction_obj.created,
        time_taken_src=mock_transaction_obj.time_taken_src,
        id=mock_transaction_obj.id,
        object=mock_transaction_obj.object,
        hyperlinks=[],
        creator=mock_transaction_obj.creator,
    )


@pytest.fixture
def mock_attachments(mock_transaction_obj: structs.TransactionStruct) -> list[structs.AttachmentObjectFromAPI]:
    assert mock_transaction_obj.ticket_obj is not None
    return [
        structs.AttachmentObjectFromAPI(
            headers="",
            created=mock_transaction_obj.created,
            id=1,
            content_type="",
            creator=mock_transaction_obj.creator,
            transaction_id=structs.GenericObjStruct("", "", ""),
            message_id="",
            hyperlinks=[],
            subject=mock_transaction_obj.ticket_obj.subject,
            parent="",
        )
    ]
