"""Tests for the assembled toner forecast response.

The arithmetic is covered in test_supply_history; this is about the shape the
report is read through - one row per cartridge, sorted into urgency bands, and
an order list a person can hand to purchasing. Zabbix is stubbed, because what
is being checked is the assembly, not the client.
"""

import pytest

from plugins.printers.services.zabbix_service import ZabbixService
from shopdb.core.models import Asset, AssetType, Communication, CommunicationType
from shopdb.core.models import Model, Vendor
from plugins.printers.models import ModelSupply, Printer


LEVELS = {
    # itemid: (name, live level, history of daily readings)
    '10': ('Black Cartridge', 1, [6, 5, 4, 3]),
    '11': ('Cyan Cartridge', 40, [100, 90, 80, 70, 60, 50, 40]),
    '12': ('Yellow Cartridge', 92, [95, 94, 93, 92]),
}

START = 1780000000


class StubZabbix(ZabbixService):
    """Answers from LEVELS, so no network and no Zabbix instance."""

    @property
    def isconfigured(self):
        return True

    @property
    def isreachable(self):
        return True

    def getsuppliesbyip_cached(self, ip):
        return [{'name': name, 'level': level, 'color': None, 'itemid': itemid}
                for itemid, (name, level, _) in LEVELS.items()]

    def getlevelhistory(self, itemids, days=90):
        return {
            itemid: [(str(START + i * 86400), str(value))
                     for i, value in enumerate(history)]
            for itemid, (_, _, history) in LEVELS.items()
            if itemid in [str(i) for i in itemids]
        }


@pytest.fixture
def printer_with_parts(app, db, monkeypatch):
    """One HP printer, three cartridges, two of them with a part on file."""
    monkeypatch.setattr('plugins.printers.api.asset_routes.ZabbixService',
                        StubZabbix)

    with app.app_context():
        vendor = Vendor(vendor='HP')
        db.session.add(vendor)
        db.session.flush()
        model = Model(modelnumber='HP M454', vendorid=vendor.vendorid)
        db.session.add(model)
        db.session.flush()

        assettype = AssetType.query.filter_by(assettype='printer').first()
        if not assettype:
            assettype = AssetType(assettype='printer', pluginname='printers',
                                  tablename='printers')
            db.session.add(assettype)
            db.session.flush()

        asset = Asset(assetnumber='PRN-1', name='WJ-PRN-0122',
                      assettypeid=assettype.assettypeid)
        db.session.add(asset)
        db.session.flush()
        db.session.add(Printer(assetid=asset.assetid, vendorid=vendor.vendorid,
                               modelnumberid=model.modelnumberid))

        comtype = CommunicationType.query.filter_by(comtype='IP').first()
        if not comtype:
            comtype = CommunicationType(comtype='IP')
            db.session.add(comtype)
            db.session.flush()
        db.session.add(Communication(assetid=asset.assetid,
                                     comtypeid=comtype.comtypeid,
                                     ipaddress='10.20.30.40', isprimary=True))

        # Black and cyan are mapped; yellow deliberately is not.
        db.session.add_all([
            ModelSupply(modelnumberid=model.modelnumberid, supplytype='toner',
                        color='black', capacitytier='standard',
                        partnumber='W2020A'),
            ModelSupply(modelnumberid=model.modelnumberid, supplytype='toner',
                        color='cyan', capacitytier='standard',
                        partnumber='W2021A'),
        ])
        db.session.commit()
        yield


def forecast(client):
    response = client.get('/api/printers/supplies/forecast?days=90')
    assert response.status_code == 200
    return response.get_json()['data']


def test_a_row_is_a_cartridge_not_a_printer(client, printer_with_parts):
    """The thing being ordered is the cartridge, so it is the unit of the
    report. Nested under a printer, a reader had to open a printer to find out
    whether anything on it needed doing."""
    data = forecast(client)

    names = sorted(c['name'] for c in data['cartridges'] + data['unestimated'])
    assert names == ['Black Cartridge', 'Cyan Cartridge', 'Yellow Cartridge']


def test_each_cartridge_carries_its_own_countdown(client, printer_with_parts):
    """The defect this replaces: one printer-level figure printed beside every
    cartridge, so a healthy one wore its neighbour's deadline."""
    data = forecast(client)
    bylevel = {c['name']: c for c in data['cartridges']}

    assert bylevel['Black Cartridge']['daysleft'] == 0      # live level 1%
    assert bylevel['Cyan Cartridge']['daysleft'] == 4       # 40% at 10%/day
    assert len({c['daysleft'] for c in data['cartridges']}) > 1


def test_cartridges_are_banded_by_urgency(client, printer_with_parts):
    data = forecast(client)
    byname = {c['name']: c['band'] for c in data['cartridges']}

    assert byname['Black Cartridge'] == 'empty'
    assert byname['Cyan Cartridge'] == 'soon'
    assert data['summary']['bands']['empty'] == 1


def test_the_order_list_names_the_part_and_the_quantity(client, printer_with_parts):
    data = forecast(client)
    byparts = {item['partnumber']: item for item in data['orderlist']}

    assert byparts['W2020A']['quantity'] == 1
    assert byparts['W2020A']['printers'][0]['printername'] == 'WJ-PRN-0122'
    assert 'W2021A' in byparts


def test_a_healthy_cartridge_stays_off_the_order_list(client, printer_with_parts):
    """Yellow at 92% is months away and belongs in the tail, not in an order."""
    data = forecast(client)

    assert all(item['color'] != 'yellow' for item in data['orderlist'])


def test_cartridges_are_sorted_soonest_first(client, printer_with_parts):
    data = forecast(client)

    daysleft = [c['daysleft'] for c in data['cartridges']]
    assert daysleft == sorted(daysleft)


def test_the_horizon_is_published_so_the_page_can_name_it(client, printer_with_parts):
    """The heading says "within N days"; N has to come from whatever decided
    the list, or the two drift apart."""
    data = forecast(client)

    assert data['horizondays'] == 14
