"""Tests for the per-type post-expiry window on the shopfloor feed.

The board used to keep every ended notification for a hardcoded 30 minutes,
flagged resolved. A card with an 8:00 end time was therefore still up at 8:29,
and in the carousel/grid/banner sections nothing marked it as ended at all. The
tail is now notificationtypes.gracewindowminutes, default 0: an end time means
the card leaves the board then, and a type opts in if its cards are worth
acknowledging afterwards.
"""

from datetime import datetime, timedelta, timezone

from plugins.notifications.models import Notification, NotificationType


def _utcnow():
    return datetime.now(timezone.utc).replace(tzinfo=None)


def _make_type(db, typename, grace=0, displaystyle='standard'):
    t = NotificationType(typename=typename, typecolor='#17a2b8', isactive=True,
                         displaystyle=displaystyle, gracewindowminutes=grace)
    db.session.add(t)
    db.session.commit()
    return t


def _make_note(db, ntype, ended_minutes_ago=None, text='msg'):
    """Shopfloor note that ended N minutes ago (None = still running)."""
    now = _utcnow()
    n = Notification(
        notificationtypeid=ntype.notificationtypeid,
        notification=text,
        businessunitid=None,
        isactive=True,
        isshopfloor=True,
        starttime=now - timedelta(hours=2),
        endtime=None if ended_minutes_ago is None
        else now - timedelta(minutes=ended_minutes_ago),
    )
    db.session.add(n)
    db.session.commit()
    return n


def _current(client):
    resp = client.get('/api/notifications/shopfloor')
    assert resp.status_code == 200, resp.get_json()
    return resp.get_json()['data']['current']


def test_default_type_drops_the_card_at_its_end_time(client, db):
    """The reported bug: an 8:00 end time meant gone at 8:30."""
    ntype = _make_type(db, 'General')
    _make_note(db, ntype, ended_minutes_ago=1)

    assert _current(client) == []


def test_new_type_defaults_to_no_tail(client, db):
    """A type created without naming the field gets 0, not a surprise tail."""
    ntype = NotificationType(typename='Fresh', typecolor='#17a2b8', isactive=True)
    db.session.add(ntype)
    db.session.commit()

    assert (ntype.gracewindowminutes or 0) == 0


def test_running_card_is_unaffected(client, db):
    ntype = _make_type(db, 'General')
    _make_note(db, ntype)

    current = _current(client)
    assert len(current) == 1
    assert current[0]['resolved'] is False


def test_type_with_a_tail_keeps_the_card_marked_resolved(client, db):
    ntype = _make_type(db, 'Incident', grace=30)
    _make_note(db, ntype, ended_minutes_ago=10)

    current = _current(client)
    assert len(current) == 1
    assert current[0]['resolved'] is True


def test_card_leaves_once_past_its_own_tail(client, db):
    ntype = _make_type(db, 'Incident', grace=30)
    _make_note(db, ntype, ended_minutes_ago=31)

    assert _current(client) == []


def test_each_type_is_held_to_its_own_window(client, db):
    """The widest tail must not leak onto a type that asked for none.

    The query widens to the largest configured window, so without the per-row
    check the no-tail type would ride along on the incident type's 60 minutes.
    """
    lingering = _make_type(db, 'Incident', grace=60)
    immediate = _make_type(db, 'General', grace=0)
    _make_note(db, lingering, ended_minutes_ago=20, text='incident')
    _make_note(db, immediate, ended_minutes_ago=20, text='general')

    texts = {c['notification'] for c in _current(client)}
    assert texts == {'incident'}


def test_carousel_type_obeys_the_window_too(client, db):
    """Recognition is a carousel card, where nothing renders `resolved`."""
    ntype = _make_type(db, 'Recognition', grace=0, displaystyle='carousel')
    _make_note(db, ntype, ended_minutes_ago=5)

    assert _current(client) == []


def test_api_rejects_a_negative_window(client, db, auth_headers):
    resp = client.post('/api/notifications/types',
                       json={'typename': 'Bad', 'gracewindowminutes': -5},
                       headers=auth_headers)

    assert resp.status_code == 400
    assert 'gracewindowminutes' in resp.get_data(as_text=True)


def test_api_round_trips_the_window(client, db, auth_headers):
    create = client.post('/api/notifications/types',
                         json={'typename': 'Incident', 'gracewindowminutes': 45},
                         headers=auth_headers)
    assert create.status_code in (200, 201), create.get_json()
    typeid = create.get_json()['data']['notificationtypeid']

    listed = client.get('/api/notifications/types', headers=auth_headers)
    row = next(t for t in listed.get_json()['data']
               if t['notificationtypeid'] == typeid)
    assert row['gracewindowminutes'] == 45

    update = client.put(f'/api/notifications/types/{typeid}',
                        json={'gracewindowminutes': 0}, headers=auth_headers)
    assert update.status_code == 200, update.get_json()
    assert update.get_json()['data']['gracewindowminutes'] == 0
