# Copyright (C) 2012 Anaconda, Inc
# SPDX-License-Identifier: BSD-3-Clause
"""Implements all conda.notices types."""

from __future__ import annotations

import hashlib
from datetime import datetime
from typing import TYPE_CHECKING, NamedTuple

from ..base.constants import NoticeLevel

if TYPE_CHECKING:
    from collections.abc import Sequence
    from pathlib import Path

#: Value to use for message ID when it is not provided
UNDEFINED_MESSAGE_ID = "undefined"


class ChannelNotice(NamedTuple):
    """Represents an individual channel notice."""

    id: str
    channel_name: str | None
    message: str | None
    level: NoticeLevel
    created_at: datetime | None
    expired_at: datetime | None
    interval: int | None

    def to_dict(self):
        return {
            "id": self.id,
            "channel_name": self.channel_name,
            "message": self.message,
            "level": self.level.name.lower(),
            "created_at": self.created_at.isoformat(),
            "expired_at": self.expired_at.isoformat(),
            "interval": self.interval,
        }


class ChannelNoticeResultSet(NamedTuple):
    """
    Represents a list of a channel notices, plus some accompanying
    metadata such as `viewed_channel_notices`.
    """

    #: Channel notices that are included in this particular set
    channel_notices: Sequence[ChannelNotice]

    #: Total number of channel notices; not just the ones that will be displayed
    total_number_channel_notices: int

    #: The number of channel notices that have already been viewed
    viewed_channel_notices: int


class ChannelNoticeResponse(NamedTuple):
    url: str
    name: str
    json_data: dict | None

    @property
    def notices(self) -> Sequence[ChannelNotice]:
        if self.json_data:
            notices = self.json_data.get("notices", ())

            return tuple(
                ChannelNotice(
                    id=str(notice.get("id", UNDEFINED_MESSAGE_ID)),
                    channel_name=self.name,
                    message=notice.get("message"),
                    level=self._parse_notice_level(notice.get("level")),
                    created_at=self._parse_iso_timestamp(notice.get("created_at")),
                    expired_at=self._parse_iso_timestamp(notice.get("expired_at")),
                    interval=notice.get("interval"),
                )
                for notice in notices
            )

        # Default value
        return ()

    @staticmethod
    def _parse_notice_level(level: str | None) -> NoticeLevel:
        """
        We use this to validate notice levels and provide reasonable defaults
        if any are invalid.
        """
        try:
            return NoticeLevel(level)
        except ValueError:
            # If we get an invalid value, rather than fail, we simply use a reasonable default
            return NoticeLevel(NoticeLevel.INFO)

    @staticmethod
    def _parse_iso_timestamp(iso_timestamp: str | None) -> datetime | None:
        """Parse ISO timestamp and fail over to a default value of none."""
        if iso_timestamp is None:
            return None
        try:
            return datetime.fromisoformat(iso_timestamp)
        except ValueError:
            return None

    @classmethod
    def get_cache_key(cls, url: str, cache_dir: Path) -> Path:
        """Returns where this channel response will be cached by hashing the URL."""
        bytes_filename = url.encode()
        sha256_hash = hashlib.sha256(bytes_filename)
        cache_filename = f"{sha256_hash.hexdigest()}.json"

        return cache_dir.joinpath(cache_filename)
