"""Test the UniFi Protect lock platform."""
# pylint: disable=protected-access
from __future__ import annotations

from copy import copy
from unittest.mock import AsyncMock, Mock

import pytest
from pyunifiprotect.data import Doorlock
from pyunifiprotect.data.types import LockStatusType

from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION
from homeassistant.const import (
    ATTR_ATTRIBUTION,
    ATTR_ENTITY_ID,
    STATE_JAMMED,
    STATE_LOCKED,
    STATE_LOCKING,
    STATE_UNAVAILABLE,
    STATE_UNLOCKED,
    STATE_UNLOCKING,
    Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er

from .conftest import MockEntityFixture, assert_entity_counts


@pytest.fixture(name="doorlock")
async def doorlock_fixture(
    hass: HomeAssistant, mock_entry: MockEntityFixture, mock_doorlock: Doorlock
):
    """Fixture for a single doorlock for testing the lock platform."""

    # disable pydantic validation so mocking can happen
    Doorlock.__config__.validate_assignment = False

    lock_obj = mock_doorlock.copy(deep=True)
    lock_obj._api = mock_entry.api
    lock_obj.name = "Test Lock"
    lock_obj.lock_status = LockStatusType.OPEN

    mock_entry.api.bootstrap.doorlocks = {
        lock_obj.id: lock_obj,
    }

    await hass.config_entries.async_setup(mock_entry.entry.entry_id)
    await hass.async_block_till_done()

    assert_entity_counts(hass, Platform.LOCK, 1, 1)

    yield (lock_obj, "lock.test_lock_lock")

    Doorlock.__config__.validate_assignment = True


async def test_lock_setup(
    hass: HomeAssistant,
    doorlock: tuple[Doorlock, str],
):
    """Test lock entity setup."""

    unique_id = f"{doorlock[0].id}_lock"
    entity_id = doorlock[1]

    entity_registry = er.async_get(hass)
    entity = entity_registry.async_get(entity_id)
    assert entity
    assert entity.unique_id == unique_id

    state = hass.states.get(entity_id)
    assert state
    assert state.state == STATE_UNLOCKED
    assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION


async def test_lock_locked(
    hass: HomeAssistant,
    mock_entry: MockEntityFixture,
    doorlock: tuple[Doorlock, str],
):
    """Test lock entity locked."""

    new_bootstrap = copy(mock_entry.api.bootstrap)
    new_lock = doorlock[0].copy()
    new_lock.lock_status = LockStatusType.CLOSED

    mock_msg = Mock()
    mock_msg.changed_data = {}
    mock_msg.new_obj = new_lock

    new_bootstrap.doorlocks = {new_lock.id: new_lock}
    mock_entry.api.bootstrap = new_bootstrap
    mock_entry.api.ws_subscription(mock_msg)
    await hass.async_block_till_done()

    state = hass.states.get(doorlock[1])
    assert state
    assert state.state == STATE_LOCKED


async def test_lock_unlocking(
    hass: HomeAssistant,
    mock_entry: MockEntityFixture,
    doorlock: tuple[Doorlock, str],
):
    """Test lock entity unlocking."""

    new_bootstrap = copy(mock_entry.api.bootstrap)
    new_lock = doorlock[0].copy()
    new_lock.lock_status = LockStatusType.OPENING

    mock_msg = Mock()
    mock_msg.changed_data = {}
    mock_msg.new_obj = new_lock

    new_bootstrap.doorlocks = {new_lock.id: new_lock}
    mock_entry.api.bootstrap = new_bootstrap
    mock_entry.api.ws_subscription(mock_msg)
    await hass.async_block_till_done()

    state = hass.states.get(doorlock[1])
    assert state
    assert state.state == STATE_UNLOCKING


async def test_lock_locking(
    hass: HomeAssistant,
    mock_entry: MockEntityFixture,
    doorlock: tuple[Doorlock, str],
):
    """Test lock entity locking."""

    new_bootstrap = copy(mock_entry.api.bootstrap)
    new_lock = doorlock[0].copy()
    new_lock.lock_status = LockStatusType.CLOSING

    mock_msg = Mock()
    mock_msg.changed_data = {}
    mock_msg.new_obj = new_lock

    new_bootstrap.doorlocks = {new_lock.id: new_lock}
    mock_entry.api.bootstrap = new_bootstrap
    mock_entry.api.ws_subscription(mock_msg)
    await hass.async_block_till_done()

    state = hass.states.get(doorlock[1])
    assert state
    assert state.state == STATE_LOCKING


async def test_lock_jammed(
    hass: HomeAssistant,
    mock_entry: MockEntityFixture,
    doorlock: tuple[Doorlock, str],
):
    """Test lock entity jammed."""

    new_bootstrap = copy(mock_entry.api.bootstrap)
    new_lock = doorlock[0].copy()
    new_lock.lock_status = LockStatusType.JAMMED_WHILE_CLOSING

    mock_msg = Mock()
    mock_msg.changed_data = {}
    mock_msg.new_obj = new_lock

    new_bootstrap.doorlocks = {new_lock.id: new_lock}
    mock_entry.api.bootstrap = new_bootstrap
    mock_entry.api.ws_subscription(mock_msg)
    await hass.async_block_till_done()

    state = hass.states.get(doorlock[1])
    assert state
    assert state.state == STATE_JAMMED


async def test_lock_unavailable(
    hass: HomeAssistant,
    mock_entry: MockEntityFixture,
    doorlock: tuple[Doorlock, str],
):
    """Test lock entity unavailable."""

    new_bootstrap = copy(mock_entry.api.bootstrap)
    new_lock = doorlock[0].copy()
    new_lock.lock_status = LockStatusType.NOT_CALIBRATED

    mock_msg = Mock()
    mock_msg.changed_data = {}
    mock_msg.new_obj = new_lock

    new_bootstrap.doorlocks = {new_lock.id: new_lock}
    mock_entry.api.bootstrap = new_bootstrap
    mock_entry.api.ws_subscription(mock_msg)
    await hass.async_block_till_done()

    state = hass.states.get(doorlock[1])
    assert state
    assert state.state == STATE_UNAVAILABLE


async def test_lock_do_lock(
    hass: HomeAssistant,
    doorlock: tuple[Doorlock, str],
):
    """Test lock entity lock service."""

    doorlock[0].__fields__["close_lock"] = Mock()
    doorlock[0].close_lock = AsyncMock()

    await hass.services.async_call(
        "lock",
        "lock",
        {ATTR_ENTITY_ID: doorlock[1]},
        blocking=True,
    )

    doorlock[0].close_lock.assert_called_once()


async def test_lock_do_unlock(
    hass: HomeAssistant,
    mock_entry: MockEntityFixture,
    doorlock: tuple[Doorlock, str],
):
    """Test lock entity unlock service."""

    new_bootstrap = copy(mock_entry.api.bootstrap)
    new_lock = doorlock[0].copy()
    new_lock.lock_status = LockStatusType.CLOSED

    mock_msg = Mock()
    mock_msg.changed_data = {}
    mock_msg.new_obj = new_lock

    new_bootstrap.doorlocks = {new_lock.id: new_lock}
    mock_entry.api.bootstrap = new_bootstrap
    mock_entry.api.ws_subscription(mock_msg)
    await hass.async_block_till_done()

    new_lock.__fields__["open_lock"] = Mock()
    new_lock.open_lock = AsyncMock()

    await hass.services.async_call(
        "lock",
        "unlock",
        {ATTR_ENTITY_ID: doorlock[1]},
        blocking=True,
    )

    new_lock.open_lock.assert_called_once()