"""The tests for the google calendar platform."""

from __future__ import annotations

import copy
from http import HTTPStatus
from typing import Any
from unittest.mock import Mock, patch

import httplib2
import pytest

from homeassistant.components.google import (
    CONF_CAL_ID,
    CONF_CLIENT_ID,
    CONF_CLIENT_SECRET,
    CONF_DEVICE_ID,
    CONF_ENTITIES,
    CONF_IGNORE_AVAILABILITY,
    CONF_NAME,
    CONF_TRACK,
    DEVICE_SCHEMA,
    SERVICE_SCAN_CALENDARS,
    do_setup,
)
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.helpers.template import DATE_STR_FORMAT
from homeassistant.setup import async_setup_component
from homeassistant.util import slugify
import homeassistant.util.dt as dt_util

from .conftest import TEST_CALENDAR

from tests.common import async_mock_service

GOOGLE_CONFIG = {CONF_CLIENT_ID: "client_id", CONF_CLIENT_SECRET: "client_secret"}
TEST_ENTITY = "calendar.we_are_we_are_a_test_calendar"
TEST_ENTITY_NAME = "We are, we are, a... Test Calendar"

TEST_EVENT = {
    "summary": "Test All Day Event",
    "start": {},
    "end": {},
    "location": "Test Cases",
    "description": "test event",
    "kind": "calendar#event",
    "created": "2016-06-23T16:37:57.000Z",
    "transparency": "transparent",
    "updated": "2016-06-24T01:57:21.045Z",
    "reminders": {"useDefault": True},
    "organizer": {
        "email": "uvrttabwegnui4gtia3vyqb@import.calendar.google.com",
        "displayName": "Organizer Name",
        "self": True,
    },
    "sequence": 0,
    "creator": {
        "email": "uvrttabwegnui4gtia3vyqb@import.calendar.google.com",
        "displayName": "Organizer Name",
        "self": True,
    },
    "id": "_c8rinwq863h45qnucyoi43ny8",
    "etag": '"2933466882090000"',
    "htmlLink": "https://www.google.com/calendar/event?eid=*******",
    "iCalUID": "cydrevtfuybguinhomj@google.com",
    "status": "confirmed",
}


def get_calendar_info(calendar):
    """Convert data from Google into DEVICE_SCHEMA."""
    calendar_info = DEVICE_SCHEMA(
        {
            CONF_CAL_ID: calendar["id"],
            CONF_ENTITIES: [
                {
                    CONF_TRACK: calendar["track"],
                    CONF_NAME: calendar["summary"],
                    CONF_DEVICE_ID: slugify(calendar["summary"]),
                    CONF_IGNORE_AVAILABILITY: calendar.get("ignore_availability", True),
                }
            ],
        }
    )
    return calendar_info


@pytest.fixture(autouse=True)
def mock_google_setup(hass, test_calendar):
    """Mock the google set up functions."""
    hass.loop.run_until_complete(async_setup_component(hass, "group", {"group": {}}))
    calendar = get_calendar_info(test_calendar)
    calendars = {calendar[CONF_CAL_ID]: calendar}
    patch_google_auth = patch(
        "homeassistant.components.google.do_authentication", side_effect=do_setup
    )
    patch_google_load = patch(
        "homeassistant.components.google.load_config", return_value=calendars
    )
    patch_google_services = patch("homeassistant.components.google.setup_services")
    async_mock_service(hass, "google", SERVICE_SCAN_CALENDARS)

    with patch_google_auth, patch_google_load, patch_google_services:
        yield


@pytest.fixture(autouse=True)
def set_time_zone():
    """Set the time zone for the tests."""
    # Set our timezone to CST/Regina so we can check calculations
    # This keeps UTC-6 all year round
    dt_util.set_default_time_zone(dt_util.get_time_zone("America/Regina"))
    yield
    dt_util.set_default_time_zone(dt_util.get_time_zone("UTC"))


@pytest.fixture(name="google_service")
def mock_google_service():
    """Mock google service."""
    patch_google_service = patch(
        "homeassistant.components.google.calendar.GoogleCalendarService"
    )
    with patch_google_service as mock_service:
        yield mock_service


async def test_all_day_event(hass, mock_next_event):
    """Test that we can create an event trigger on device."""
    week_from_today = dt_util.dt.date.today() + dt_util.dt.timedelta(days=7)
    end_event = week_from_today + dt_util.dt.timedelta(days=1)
    event = copy.deepcopy(TEST_EVENT)
    start = week_from_today.isoformat()
    end = end_event.isoformat()
    event["start"]["date"] = start
    event["end"]["date"] = end
    mock_next_event.return_value.event = event

    assert await async_setup_component(hass, "google", {"google": GOOGLE_CONFIG})
    await hass.async_block_till_done()

    state = hass.states.get(TEST_ENTITY)
    assert state.name == TEST_ENTITY_NAME
    assert state.state == STATE_OFF
    assert dict(state.attributes) == {
        "friendly_name": TEST_ENTITY_NAME,
        "message": event["summary"],
        "all_day": True,
        "offset_reached": False,
        "start_time": week_from_today.strftime(DATE_STR_FORMAT),
        "end_time": end_event.strftime(DATE_STR_FORMAT),
        "location": event["location"],
        "description": event["description"],
    }


async def test_future_event(hass, mock_next_event):
    """Test that we can create an event trigger on device."""
    one_hour_from_now = dt_util.now() + dt_util.dt.timedelta(minutes=30)
    end_event = one_hour_from_now + dt_util.dt.timedelta(minutes=60)
    start = one_hour_from_now.isoformat()
    end = end_event.isoformat()
    event = copy.deepcopy(TEST_EVENT)
    event["start"]["dateTime"] = start
    event["end"]["dateTime"] = end
    mock_next_event.return_value.event = event

    assert await async_setup_component(hass, "google", {"google": GOOGLE_CONFIG})
    await hass.async_block_till_done()

    state = hass.states.get(TEST_ENTITY)
    assert state.name == TEST_ENTITY_NAME
    assert state.state == STATE_OFF
    assert dict(state.attributes) == {
        "friendly_name": TEST_ENTITY_NAME,
        "message": event["summary"],
        "all_day": False,
        "offset_reached": False,
        "start_time": one_hour_from_now.strftime(DATE_STR_FORMAT),
        "end_time": end_event.strftime(DATE_STR_FORMAT),
        "location": event["location"],
        "description": event["description"],
    }


async def test_in_progress_event(hass, mock_next_event):
    """Test that we can create an event trigger on device."""
    middle_of_event = dt_util.now() - dt_util.dt.timedelta(minutes=30)
    end_event = middle_of_event + dt_util.dt.timedelta(minutes=60)
    start = middle_of_event.isoformat()
    end = end_event.isoformat()
    event = copy.deepcopy(TEST_EVENT)
    event["start"]["dateTime"] = start
    event["end"]["dateTime"] = end
    mock_next_event.return_value.event = event

    assert await async_setup_component(hass, "google", {"google": GOOGLE_CONFIG})
    await hass.async_block_till_done()

    state = hass.states.get(TEST_ENTITY)
    assert state.name == TEST_ENTITY_NAME
    assert state.state == STATE_ON
    assert dict(state.attributes) == {
        "friendly_name": TEST_ENTITY_NAME,
        "message": event["summary"],
        "all_day": False,
        "offset_reached": False,
        "start_time": middle_of_event.strftime(DATE_STR_FORMAT),
        "end_time": end_event.strftime(DATE_STR_FORMAT),
        "location": event["location"],
        "description": event["description"],
    }


async def test_offset_in_progress_event(hass, mock_next_event):
    """Test that we can create an event trigger on device."""
    middle_of_event = dt_util.now() + dt_util.dt.timedelta(minutes=14)
    end_event = middle_of_event + dt_util.dt.timedelta(minutes=60)
    start = middle_of_event.isoformat()
    end = end_event.isoformat()
    event_summary = "Test Event in Progress"
    event = copy.deepcopy(TEST_EVENT)
    event["start"]["dateTime"] = start
    event["end"]["dateTime"] = end
    event["summary"] = f"{event_summary} !!-15"
    mock_next_event.return_value.event = event

    assert await async_setup_component(hass, "google", {"google": GOOGLE_CONFIG})
    await hass.async_block_till_done()

    state = hass.states.get(TEST_ENTITY)
    assert state.name == TEST_ENTITY_NAME
    assert state.state == STATE_OFF
    assert dict(state.attributes) == {
        "friendly_name": TEST_ENTITY_NAME,
        "message": event_summary,
        "all_day": False,
        "offset_reached": True,
        "start_time": middle_of_event.strftime(DATE_STR_FORMAT),
        "end_time": end_event.strftime(DATE_STR_FORMAT),
        "location": event["location"],
        "description": event["description"],
    }


@pytest.mark.skip
async def test_all_day_offset_in_progress_event(hass, mock_next_event):
    """Test that we can create an event trigger on device."""
    tomorrow = dt_util.dt.date.today() + dt_util.dt.timedelta(days=1)
    end_event = tomorrow + dt_util.dt.timedelta(days=1)
    start = tomorrow.isoformat()
    end = end_event.isoformat()
    event_summary = "Test All Day Event Offset In Progress"
    event = copy.deepcopy(TEST_EVENT)
    event["start"]["date"] = start
    event["end"]["date"] = end
    event["summary"] = f"{event_summary} !!-25:0"
    mock_next_event.return_value.event = event

    assert await async_setup_component(hass, "google", {"google": GOOGLE_CONFIG})
    await hass.async_block_till_done()

    state = hass.states.get(TEST_ENTITY)
    assert state.name == TEST_ENTITY_NAME
    assert state.state == STATE_OFF
    assert dict(state.attributes) == {
        "friendly_name": TEST_ENTITY_NAME,
        "message": event_summary,
        "all_day": True,
        "offset_reached": True,
        "start_time": tomorrow.strftime(DATE_STR_FORMAT),
        "end_time": end_event.strftime(DATE_STR_FORMAT),
        "location": event["location"],
        "description": event["description"],
    }


async def test_all_day_offset_event(hass, mock_next_event):
    """Test that we can create an event trigger on device."""
    tomorrow = dt_util.dt.date.today() + dt_util.dt.timedelta(days=2)
    end_event = tomorrow + dt_util.dt.timedelta(days=1)
    start = tomorrow.isoformat()
    end = end_event.isoformat()
    offset_hours = 1 + dt_util.now().hour
    event_summary = "Test All Day Event Offset"
    event = copy.deepcopy(TEST_EVENT)
    event["start"]["date"] = start
    event["end"]["date"] = end
    event["summary"] = f"{event_summary} !!-{offset_hours}:0"
    mock_next_event.return_value.event = event

    assert await async_setup_component(hass, "google", {"google": GOOGLE_CONFIG})
    await hass.async_block_till_done()

    state = hass.states.get(TEST_ENTITY)
    assert state.name == TEST_ENTITY_NAME
    assert state.state == STATE_OFF
    assert dict(state.attributes) == {
        "friendly_name": TEST_ENTITY_NAME,
        "message": event_summary,
        "all_day": True,
        "offset_reached": False,
        "start_time": tomorrow.strftime(DATE_STR_FORMAT),
        "end_time": end_event.strftime(DATE_STR_FORMAT),
        "location": event["location"],
        "description": event["description"],
    }


async def test_update_error(hass, google_service):
    """Test that the calendar handles a server error."""
    google_service.return_value.get = Mock(
        side_effect=httplib2.ServerNotFoundError("unit test")
    )
    assert await async_setup_component(hass, "google", {"google": GOOGLE_CONFIG})
    await hass.async_block_till_done()

    state = hass.states.get(TEST_ENTITY)
    assert state.name == TEST_ENTITY_NAME
    assert state.state == "off"


async def test_calendars_api(hass, hass_client, google_service):
    """Test the Rest API returns the calendar."""
    assert await async_setup_component(hass, "google", {"google": GOOGLE_CONFIG})
    await hass.async_block_till_done()

    client = await hass_client()
    response = await client.get("/api/calendars")
    assert response.status == HTTPStatus.OK
    data = await response.json()
    assert data == [
        {
            "entity_id": TEST_ENTITY,
            "name": TEST_ENTITY_NAME,
        }
    ]


async def test_http_event_api_failure(hass, hass_client, google_service):
    """Test the Rest API response during a calendar failure."""
    google_service.return_value.get = Mock(
        side_effect=httplib2.ServerNotFoundError("unit test")
    )

    assert await async_setup_component(hass, "google", {"google": GOOGLE_CONFIG})
    await hass.async_block_till_done()

    start = dt_util.now().isoformat()
    end = (dt_util.now() + dt_util.dt.timedelta(minutes=60)).isoformat()

    client = await hass_client()
    response = await client.get(f"/api/calendars/{TEST_ENTITY}?start={start}&end={end}")
    assert response.status == HTTPStatus.OK
    # A failure to talk to the server results in an empty list of events
    events = await response.json()
    assert events == []


async def test_http_api_event(hass, hass_client, google_service, mock_events_list):
    """Test querying the API and fetching events from the server."""
    now = dt_util.now()

    mock_events_list(
        {
            "items": [
                {
                    "summary": "Event title",
                    "start": {"dateTime": now.isoformat()},
                    "end": {
                        "dateTime": (now + dt_util.dt.timedelta(minutes=5)).isoformat()
                    },
                }
            ],
        }
    )
    assert await async_setup_component(hass, "google", {"google": GOOGLE_CONFIG})
    await hass.async_block_till_done()

    start = (now - dt_util.dt.timedelta(minutes=60)).isoformat()
    end = (now + dt_util.dt.timedelta(minutes=60)).isoformat()

    client = await hass_client()
    response = await client.get(f"/api/calendars/{TEST_ENTITY}?start={start}&end={end}")
    assert response.status == HTTPStatus.OK
    events = await response.json()
    assert len(events) == 1
    assert "summary" in events[0]
    assert events[0]["summary"] == "Event title"


def create_ignore_avail_calendar() -> dict[str, Any]:
    """Create a calendar with ignore_availability set."""
    calendar = TEST_CALENDAR.copy()
    calendar["ignore_availability"] = False
    return calendar


@pytest.mark.parametrize("test_calendar", [create_ignore_avail_calendar()])
async def test_opaque_event(hass, hass_client, google_service, mock_events_list):
    """Test querying the API and fetching events from the server."""
    now = dt_util.now()

    mock_events_list(
        {
            "items": [
                {
                    "summary": "Event title",
                    "transparency": "opaque",
                    "start": {"dateTime": now.isoformat()},
                    "end": {
                        "dateTime": (now + dt_util.dt.timedelta(minutes=5)).isoformat()
                    },
                }
            ],
        }
    )
    assert await async_setup_component(hass, "google", {"google": GOOGLE_CONFIG})
    await hass.async_block_till_done()

    start = (now - dt_util.dt.timedelta(minutes=60)).isoformat()
    end = (now + dt_util.dt.timedelta(minutes=60)).isoformat()

    client = await hass_client()
    response = await client.get(f"/api/calendars/{TEST_ENTITY}?start={start}&end={end}")
    assert response.status == HTTPStatus.OK
    events = await response.json()
    assert len(events) == 1
    assert "summary" in events[0]
    assert events[0]["summary"] == "Event title"


@pytest.mark.parametrize("test_calendar", [create_ignore_avail_calendar()])
async def test_transparent_event(hass, hass_client, google_service, mock_events_list):
    """Test querying the API and fetching events from the server."""
    now = dt_util.now()

    mock_events_list(
        {
            "items": [
                {
                    "summary": "Event title",
                    "transparency": "transparent",
                    "start": {"dateTime": now.isoformat()},
                    "end": {
                        "dateTime": (now + dt_util.dt.timedelta(minutes=5)).isoformat()
                    },
                }
            ],
        }
    )
    assert await async_setup_component(hass, "google", {"google": GOOGLE_CONFIG})
    await hass.async_block_till_done()

    start = (now - dt_util.dt.timedelta(minutes=60)).isoformat()
    end = (now + dt_util.dt.timedelta(minutes=60)).isoformat()

    client = await hass_client()
    response = await client.get(f"/api/calendars/{TEST_ENTITY}?start={start}&end={end}")
    assert response.status == HTTPStatus.OK
    events = await response.json()
    assert events == []