"""The tests for the notify file platform."""

import os
from typing import Any
from unittest.mock import MagicMock, call, mock_open, patch

from freezegun.api import FrozenDateTimeFactory
import pytest

from homeassistant.components import notify
from homeassistant.components.file import DOMAIN
from homeassistant.components.notify import ATTR_TITLE_DEFAULT
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.typing import ConfigType
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util

from tests.common import MockConfigEntry, assert_setup_component


async def test_bad_config(hass: HomeAssistant) -> None:
    """Test set up the platform with bad/missing config."""
    config = {notify.DOMAIN: {"name": "test", "platform": "file"}}
    with assert_setup_component(0, domain="notify") as handle_config:
        assert await async_setup_component(hass, notify.DOMAIN, config)
        await hass.async_block_till_done()
    assert not handle_config[notify.DOMAIN]


@pytest.mark.parametrize(
    ("domain", "service", "params"),
    [
        (notify.DOMAIN, "test", {"message": "one, two, testing, testing"}),
        (
            notify.DOMAIN,
            "send_message",
            {"entity_id": "notify.test", "message": "one, two, testing, testing"},
        ),
    ],
    ids=["legacy", "entity"],
)
@pytest.mark.parametrize(
    ("timestamp", "config"),
    [
        (
            False,
            {
                "notify": [
                    {
                        "name": "test",
                        "platform": "file",
                        "filename": "mock_file",
                        "timestamp": False,
                    }
                ]
            },
        ),
        (
            True,
            {
                "notify": [
                    {
                        "name": "test",
                        "platform": "file",
                        "filename": "mock_file",
                        "timestamp": True,
                    }
                ]
            },
        ),
    ],
    ids=["no_timestamp", "timestamp"],
)
async def test_notify_file(
    hass: HomeAssistant,
    freezer: FrozenDateTimeFactory,
    timestamp: bool,
    mock_is_allowed_path: MagicMock,
    config: ConfigType,
    domain: str,
    service: str,
    params: dict[str, str],
) -> None:
    """Test the notify file output."""
    filename = "mock_file"
    message = params["message"]
    assert await async_setup_component(hass, notify.DOMAIN, config)
    await hass.async_block_till_done()
    assert await async_setup_component(hass, DOMAIN, config)
    await hass.async_block_till_done(wait_background_tasks=True)

    freezer.move_to(dt_util.utcnow())

    m_open = mock_open()
    with (
        patch("homeassistant.components.file.notify.open", m_open, create=True),
        patch("homeassistant.components.file.notify.os.stat") as mock_st,
    ):
        mock_st.return_value.st_size = 0
        title = (
            f"{ATTR_TITLE_DEFAULT} notifications "
            f"(Log started: {dt_util.utcnow().isoformat()})\n{'-' * 80}\n"
        )

        await hass.services.async_call(domain, service, params, blocking=True)

        full_filename = os.path.join(hass.config.path(), filename)
        assert m_open.call_count == 1
        assert m_open.call_args == call(full_filename, "a", encoding="utf8")

        assert m_open.return_value.write.call_count == 2
        if not timestamp:
            assert m_open.return_value.write.call_args_list == [
                call(title),
                call(f"{message}\n"),
            ]
        else:
            assert m_open.return_value.write.call_args_list == [
                call(title),
                call(f"{dt_util.utcnow().isoformat()} {message}\n"),
            ]


@pytest.mark.parametrize(
    ("domain", "service", "params"),
    [(notify.DOMAIN, "test", {"message": "one, two, testing, testing"})],
    ids=["legacy"],
)
@pytest.mark.parametrize(
    ("is_allowed", "config"),
    [
        (
            True,
            {
                "notify": [
                    {
                        "name": "test",
                        "platform": "file",
                        "filename": "mock_file",
                    }
                ]
            },
        ),
    ],
    ids=["allowed_but_access_failed"],
)
async def test_legacy_notify_file_exception(
    hass: HomeAssistant,
    freezer: FrozenDateTimeFactory,
    mock_is_allowed_path: MagicMock,
    config: ConfigType,
    domain: str,
    service: str,
    params: dict[str, str],
) -> None:
    """Test legacy notify file output has exception."""
    assert await async_setup_component(hass, notify.DOMAIN, config)
    await hass.async_block_till_done()
    assert await async_setup_component(hass, DOMAIN, config)
    await hass.async_block_till_done(wait_background_tasks=True)

    freezer.move_to(dt_util.utcnow())

    m_open = mock_open()
    with (
        patch("homeassistant.components.file.notify.open", m_open, create=True),
        patch("homeassistant.components.file.notify.os.stat") as mock_st,
    ):
        mock_st.side_effect = OSError("Access Failed")
        with pytest.raises(ServiceValidationError) as exc:
            await hass.services.async_call(domain, service, params, blocking=True)
        assert f"{exc.value!r}" == "ServiceValidationError('write_access_failed')"


@pytest.mark.parametrize(
    ("timestamp", "data"),
    [
        (
            False,
            {
                "name": "test",
                "platform": "notify",
                "file_path": "mock_file",
                "timestamp": False,
            },
        ),
        (
            True,
            {
                "name": "test",
                "platform": "notify",
                "file_path": "mock_file",
                "timestamp": True,
            },
        ),
    ],
    ids=["no_timestamp", "timestamp"],
)
async def test_legacy_notify_file_entry_only_setup(
    hass: HomeAssistant,
    freezer: FrozenDateTimeFactory,
    timestamp: bool,
    mock_is_allowed_path: MagicMock,
    data: dict[str, Any],
) -> None:
    """Test the legacy notify file output in entry only setup."""
    filename = "mock_file"

    domain = notify.DOMAIN
    service = "test"
    params = {"message": "one, two, testing, testing"}
    message = params["message"]

    entry = MockConfigEntry(
        domain=DOMAIN, data=data, title=f"test [{data['file_path']}]"
    )
    entry.add_to_hass(hass)
    await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()
    await hass.async_block_till_done(wait_background_tasks=True)

    freezer.move_to(dt_util.utcnow())

    m_open = mock_open()
    with (
        patch("homeassistant.components.file.notify.open", m_open, create=True),
        patch("homeassistant.components.file.notify.os.stat") as mock_st,
    ):
        mock_st.return_value.st_size = 0
        title = (
            f"{ATTR_TITLE_DEFAULT} notifications "
            f"(Log started: {dt_util.utcnow().isoformat()})\n{'-' * 80}\n"
        )

        await hass.services.async_call(domain, service, params, blocking=True)

        assert m_open.call_count == 1
        assert m_open.call_args == call(filename, "a", encoding="utf8")

        assert m_open.return_value.write.call_count == 2
        if not timestamp:
            assert m_open.return_value.write.call_args_list == [
                call(title),
                call(f"{message}\n"),
            ]
        else:
            assert m_open.return_value.write.call_args_list == [
                call(title),
                call(f"{dt_util.utcnow().isoformat()} {message}\n"),
            ]


@pytest.mark.parametrize(
    ("is_allowed", "config"),
    [
        (
            False,
            {
                "name": "test",
                "platform": "notify",
                "file_path": "mock_file",
                "timestamp": False,
            },
        ),
    ],
    ids=["not_allowed"],
)
async def test_legacy_notify_file_not_allowed(
    hass: HomeAssistant,
    caplog: pytest.LogCaptureFixture,
    mock_is_allowed_path: MagicMock,
    config: dict[str, Any],
) -> None:
    """Test legacy notify file output not allowed."""
    entry = MockConfigEntry(
        domain=DOMAIN, data=config, title=f"test [{config['file_path']}]"
    )
    entry.add_to_hass(hass)
    assert not await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done(wait_background_tasks=True)
    assert "is not allowed" in caplog.text


@pytest.mark.parametrize(
    ("service", "params"),
    [
        ("test", {"message": "one, two, testing, testing"}),
        (
            "send_message",
            {"entity_id": "notify.test", "message": "one, two, testing, testing"},
        ),
    ],
)
@pytest.mark.parametrize(
    ("data", "is_allowed"),
    [
        (
            {
                "name": "test",
                "platform": "notify",
                "file_path": "mock_file",
                "timestamp": False,
            },
            True,
        ),
    ],
    ids=["not_allowed"],
)
async def test_notify_file_write_access_failed(
    hass: HomeAssistant,
    freezer: FrozenDateTimeFactory,
    mock_is_allowed_path: MagicMock,
    service: str,
    params: dict[str, Any],
    data: dict[str, Any],
) -> None:
    """Test the notify file fails."""
    domain = notify.DOMAIN

    entry = MockConfigEntry(
        domain=DOMAIN, data=data, title=f"test [{data['file_path']}]"
    )
    entry.add_to_hass(hass)
    await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()
    await hass.async_block_till_done(wait_background_tasks=True)

    freezer.move_to(dt_util.utcnow())

    m_open = mock_open()
    with (
        patch("homeassistant.components.file.notify.open", m_open, create=True),
        patch("homeassistant.components.file.notify.os.stat") as mock_st,
    ):
        mock_st.side_effect = OSError("Access Failed")
        with pytest.raises(ServiceValidationError) as exc:
            await hass.services.async_call(domain, service, params, blocking=True)
        assert f"{exc.value!r}" == "ServiceValidationError('write_access_failed')"
