# (c) cavaliba.com - app_cmt - common.py

from datetime import datetime, timedelta

from django.db.models import Sum
from django.utils import timezone

from app_cmt.models import CMTDay, CMTHour, CMTRaw
from app_data.data import Instance
from app_home.configuration import get_configuration

# --------------------------------------------------------
# Helpers
# --------------------------------------------------------


def get_check(iname=None):
    """Return a single Check built from the _cmtcheck instance with keyname=iname,
    or None if not found."""

    instance = Instance.from_keyname(classname="_cmtcheck", keyname=iname)
    if not instance:
        return
    return Check(instance=instance)


def get_checks():
    """Return a list of Check for every enabled _cmtcheck instance."""

    reply = []
    for instance in Instance.list_classname(classname="_cmtcheck", enabled="yes"):
        reply.append(Check(instance=instance))
    return reply


def get_check_names():
    """Return the list of keynames of every enabled _cmtcheck instance."""

    return [i.keyname for i in Instance.list_classname(classname="_cmtcheck", enabled="yes")]


# -----------------------------------------------
# live
# -----------------------------------------------
def compute_live_status(checks=None):
    """Set check.live_status ('OK'/'KO'/'NA') from the most recent CMTRaw row
    (within the last 5 minutes) for each check."""

    if not checks:
        checks = get_checks()

    delta = timezone.now() - timedelta(minutes=5)
    raws = CMTRaw.objects.filter(timestamp__gte=delta).order_by("-timestamp")

    for check in checks:
        check.live_status = "NA"
        for raw in raws:
            if raw.checkname == check.keyname:
                if check.live_status == "NA":
                    if raw.status:
                        check.live_status = "OK"
                    else:
                        check.live_status = "KO"
                    break

    return checks


# -----------------------------------------------
# hourly
# -----------------------------------------------
def compute_hourly_ratio(checks=None):
    """
    out:  dict {} : checkname => {'OK':int, 'KO':int, 'ratio':int}
    """
    if not checks:
        checks = get_checks()

    delta = timezone.now() - timedelta(minutes=60)
    raws = CMTRaw.objects.filter(timestamp__gte=delta)

    table = {}
    #   compute to buckets
    for raw in raws:
        if raw.checkname not in table:
            table[raw.checkname] = {}
            table[raw.checkname]["OK"] = 0
            table[raw.checkname]["KO"] = 0
        if raw.status:
            table[raw.checkname]["OK"] += 1
        else:
            table[raw.checkname]["KO"] += 1

    #   compute ratio
    for m, _nouse in table.items():
        d = table[m]["OK"] + table[m]["KO"]
        if d > 0:
            table[m]["ratio"] = table[m]["OK"] / d
        else:
            table[m]["ratio"] = -1

    # ratio & pretty
    for check in checks:
        check.hourly_ratio = -1
        check.hourly_ratio_pretty = "NA"

        if check.keyname not in table:
            continue

        ratio = table[check.keyname]["ratio"]

        if ratio >= 1:
            pretty = "100 %"
        elif ratio >= 0:
            pretty = f"{100 * ratio:.0f} %"
        else:
            pretty = "NA"

        check.hourly_ratio = ratio
        check.hourly_ratio_pretty = pretty

    return


def compute_hourly_bargraph(checks=None):
    """
    out:  []  'minute':int(0-59), 'ok':int, 'ko':int, 'ratio':float|None
    """

    WIDTH = 15

    if not checks:
        checks = get_checks()

    for check in checks:
        now = timezone.now()  # django aware (UTC with TZ convert for rendering)
        now_local = datetime.now()
        now1h = now - timedelta(minutes=60)
        raws = CMTRaw.objects.filter(checkname=check.keyname, timestamp__gte=now1h)

        bargraph = {}
        for i in range(60):
            bargraph[i] = {}
            bargraph[i]["OK"] = 0
            bargraph[i]["KO"] = 0
            bargraph[i]["ratio"] = -1
            bargraph[i]["tooltip"] = "NA"
            bargraph[i]["color"] = "grey"
            # x, width

        for raw in raws:
            diff = now - raw.timestamp
            diff_seconds = diff.seconds
            minute = int(diff_seconds / 60)
            if minute < 0 or minute > 59:
                continue
            if raw.status:
                bargraph[minute]["OK"] += 1
            else:
                bargraph[minute]["KO"] += 1

        # compute & pretty
        for i in range(60):
            #   ratio
            d = bargraph[i]["OK"] + bargraph[i]["KO"]
            if d > 0:
                bargraph[i]["ratio"] = bargraph[i]["OK"] / d
            else:
                bargraph[i]["ratio"] = -1

            # tootlip
            tooltip_time = (now_local - timedelta(minutes=i)).strftime("%H:%M")
            if bargraph[i]["ratio"] >= 1:
                tooltip_ratio = "100 %"
            elif bargraph[i]["ratio"] >= 0:
                tooltip_ratio = f"{100 * bargraph[i]['ratio']:.0f} %"
            else:
                tooltip_ratio = "NA"
            bargraph[i]["tooltip"] = f"{tooltip_time} - {tooltip_ratio}"

            #   x
            bargraph[i]["x"] = (59 - i) * WIDTH
            bargraph[i]["width"] = WIDTH - 3

            #   color
            color = "grey"
            if bargraph[i]["ratio"] >= 1:
                color = "green100"
            if bargraph[i]["ratio"] > 0:
                color = "green"
            else:
                color = "red"
            bargraph[i]["color"] = color

        check.hourly_bargraph = bargraph

    return checks


# -----------------------------------------------
# daily
# -----------------------------------------------
def compute_daily_ratio(checks=None):
    """
    out:  dict {} : checkname => {'OK':int, 'KO':int, 'ratio':int}
    """
    if not checks:
        checks = get_checks()

    delta = timezone.now() - timedelta(hours=24)
    samples = CMTHour.objects.filter(timestamp__gte=delta)

    table = {}
    #   compute to buckets
    for sample in samples:
        if sample.checkname not in table:
            table[sample.checkname] = {}
            table[sample.checkname]["OK"] = 0
            table[sample.checkname]["KO"] = 0
        if sample.count_ok:
            table[sample.checkname]["OK"] += sample.count_ok
        if sample.count_ko:
            table[sample.checkname]["KO"] += sample.count_ko

    #   compute ratio i=hour
    for i, _nouse in table.items():
        d = table[i]["OK"] + table[i]["KO"]
        if d > 0:
            table[i]["ratio"] = table[i]["OK"] / d
        else:
            table[i]["ratio"] = -1

    # ratio & pretty
    for check in checks:
        check.daily_ratio = -1
        check.daily_ratio_pretty = "NA"

        if check.keyname not in table:
            continue

        ratio = table[check.keyname]["ratio"]

        if ratio >= 1:
            pretty = "100 %"
        elif ratio >= 0:
            pretty = f"{100 * ratio:.2f} %"
        else:
            pretty = "NA"

        check.daily_ratio = ratio
        check.daily_ratio_pretty = pretty

    return


def compute_daily_bargraph(checks=None):
    """
    out:  []  'hour':int(0-23), 'ok':int, 'ko':int, 'ratio':float|-1
    """

    WIDTH = 30

    if not checks:
        checks = get_checks()

    for check in checks:
        now = timezone.now()  # django aware (UTC with TZ convert for rendering)

        first_hour = (
            now
            - timedelta(minutes=now.minute)
            - timedelta(seconds=now.second)
            - timedelta(microseconds=now.microsecond)
            - timedelta(hours=24)
        )

        tmp = datetime.now()
        now_local = (
            tmp
            - timedelta(minutes=tmp.minute)
            - timedelta(seconds=tmp.second)
            - timedelta(microseconds=tmp.microsecond)
        )

        samples = CMTHour.objects.filter(checkname=check.keyname, timestamp__gt=first_hour)

        bargraph = {}
        for i in range(24):
            bargraph[i] = {}
            bargraph[i]["OK"] = 0
            bargraph[i]["KO"] = 0
            bargraph[i]["ratio"] = -1
            bargraph[i]["tooltip"] = "NA"
            bargraph[i]["color"] = "grey"
            # x, width

        for sample in samples:
            diff = first_hour - sample.timestamp
            diff_seconds = diff.seconds
            hour = int(diff_seconds / 3600)

            if hour < 0 or hour > 23:
                continue
            if sample.count_ok:
                bargraph[hour]["OK"] += sample.count_ok
            if sample.count_ko:
                bargraph[hour]["KO"] += sample.count_ko

        # compute & pretty
        for i in range(24):
            #   ratio
            evtcount = bargraph[i]["OK"] + bargraph[i]["KO"]
            if evtcount > 0:
                bargraph[i]["ratio"] = bargraph[i]["OK"] / evtcount
            else:
                bargraph[i]["ratio"] = -1

            # tootlip
            tooltip_time = (now_local - timedelta(hours=i)).strftime("%H:%M")
            if bargraph[i]["ratio"] >= 1:
                tooltip_ratio = "100 %"
            elif bargraph[i]["ratio"] >= 0:
                tooltip_ratio = f"{100 * bargraph[i]['ratio']:.2f} %"
            else:
                tooltip_ratio = "NA"
            bargraph[i]["tooltip"] = f"{tooltip_time} - {tooltip_ratio} - {evtcount} evt"

            #   x
            bargraph[i]["x"] = (23 - i) * WIDTH
            bargraph[i]["width"] = WIDTH - 3

            #   color
            color = "grey"
            if bargraph[i]["ratio"] >= 1:
                color = "green100"
            elif bargraph[i]["ratio"] > 0.99:
                color = "green"
            elif bargraph[i]["ratio"] > 0.98:
                color = "orange"
            elif bargraph[i]["ratio"] >= 0:
                color = "red"
            bargraph[i]["color"] = color

        check.daily_bargraph = bargraph

    return checks


# -----------------------------------------------
# monthly / 31d
# -----------------------------------------------
def compute_monthly_ratio(checks=None):
    """
    out:  dict {} : checkname => {'OK':int, 'KO':int, 'ratio':int}
    """
    if not checks:
        checks = get_checks()

    delta = timezone.now() - timedelta(days=31)
    samples = CMTDay.objects.filter(timestamp__gte=delta)

    table = {}
    #   compute to buckets
    for sample in samples:
        if sample.checkname not in table:
            table[sample.checkname] = {}
            table[sample.checkname]["OK"] = 0
            table[sample.checkname]["KO"] = 0
        if sample.count_ok:
            table[sample.checkname]["OK"] += sample.count_ok
        if sample.count_ko:
            table[sample.checkname]["KO"] += sample.count_ko

    #   compute ratio
    for check, _nouse in table.items():
        d = table[check]["OK"] + table[check]["KO"]
        if d > 0:
            table[check]["ratio"] = table[check]["OK"] / d
        else:
            table[check]["ratio"] = -1

    # ratio & pretty
    for check in checks:
        check.monthly_ratio = -1
        check.monthly_ratio_pretty = "NA"

        if check.keyname not in table:
            continue

        ratio = table[check.keyname]["ratio"]

        if ratio >= 1:
            pretty = "100 %"
        elif ratio >= 0:
            pretty = f"{100 * ratio:.2f} %"
        else:
            pretty = "NA"

        check.monthly_ratio = ratio
        check.monthly_ratio_pretty = pretty

    return


def compute_monthly_bargraph(checks=None):
    """
    out:  []  'day':int(0-30), 'ok':int, 'ko':int, 'ratio':float|-1
    """

    WIDTH = 26

    if not checks:
        checks = get_checks()

    for check in checks:
        now = timezone.now()  # django aware (UTC with TZ convert for rendering)

        first_day = (
            now
            - timedelta(days=31)
            - timedelta(hours=now.hour)
            - timedelta(minutes=now.minute)
            - timedelta(seconds=now.second)
            - timedelta(microseconds=now.microsecond)
        )

        tmp = datetime.now()
        now_local = (
            tmp
            - timedelta(hours=tmp.hour)
            - timedelta(minutes=tmp.minute)
            - timedelta(seconds=tmp.second)
            - timedelta(microseconds=tmp.microsecond)
        )

        samples = CMTDay.objects.filter(checkname=check.keyname, timestamp__gt=first_day)

        bargraph = {}
        for i in range(31):
            bargraph[i] = {}
            bargraph[i]["OK"] = 0
            bargraph[i]["KO"] = 0
            bargraph[i]["ratio"] = -1
            bargraph[i]["tooltip"] = "NA"
            bargraph[i]["color"] = "grey"
            # x, width

        for sample in samples:
            diff = sample.timestamp - first_day
            day = 31 - diff.days

            if day < 0 or day > 30:
                continue
            if sample.count_ok:
                bargraph[day]["OK"] += sample.count_ok
            if sample.count_ko:
                bargraph[day]["KO"] += sample.count_ko

        # compute & pretty
        for i in range(31):
            #   ratio
            evtcount = bargraph[i]["OK"] + bargraph[i]["KO"]
            if evtcount > 0:
                bargraph[i]["ratio"] = bargraph[i]["OK"] / evtcount
            else:
                bargraph[i]["ratio"] = -1

            # tootlip
            tooltip_time = (now_local - timedelta(days=i)).strftime("%Y-%m-%d")
            if bargraph[i]["ratio"] >= 1:
                tooltip_ratio = "100 %"
            elif bargraph[i]["ratio"] >= 0:
                tooltip_ratio = f"{100 * bargraph[i]['ratio']:.2f} %"
            else:
                tooltip_ratio = "NA"
            bargraph[i]["tooltip"] = f"{tooltip_time} - {tooltip_ratio} - {evtcount} evt"

            #   x
            bargraph[i]["x"] = (30 - i) * WIDTH
            bargraph[i]["width"] = WIDTH - 3

            #   color
            color = "grey"
            if bargraph[i]["ratio"] >= 1:
                color = "green100"
            elif bargraph[i]["ratio"] > 0.99:
                color = "green"
            elif bargraph[i]["ratio"] > 0.98:
                color = "orange"
            elif bargraph[i]["ratio"] >= 0:
                color = "red"
            bargraph[i]["color"] = color

        check.monthly_bargraph = bargraph

    return checks


# -----------------------------------------------
# quaterly - 90D
# -----------------------------------------------
def compute_quaterly_ratio(checks=None):
    """
    out:  dict {} : checkname => {'OK':int, 'KO':int, 'ratio':int}
    """
    if not checks:
        checks = get_checks()

    delta = timezone.now() - timedelta(days=90)
    samples = CMTDay.objects.filter(timestamp__gte=delta)

    table = {}
    #   compute to buckets
    for sample in samples:
        if sample.checkname not in table:
            table[sample.checkname] = {}
            table[sample.checkname]["OK"] = 0
            table[sample.checkname]["KO"] = 0
        if sample.count_ok:
            table[sample.checkname]["OK"] += sample.count_ok
        if sample.count_ko:
            table[sample.checkname]["KO"] += sample.count_ko

    #   compute ratio
    for check, _nouse in table.items():
        d = table[check]["OK"] + table[check]["KO"]
        if d > 0:
            table[check]["ratio"] = table[check]["OK"] / d
        else:
            table[check]["ratio"] = -1

    # ratio & pretty
    for check in checks:
        check.quaterly_ratio = -1
        check.quaterly_ratio_pretty = "NA"

        if check.keyname not in table:
            continue

        ratio = table[check.keyname]["ratio"]

        if ratio >= 1:
            pretty = "100 %"
        elif ratio >= 0:
            pretty = f"{100 * ratio:.2f} %"
        else:
            pretty = "NA"

        check.quaterly_ratio = ratio
        check.quaterly_ratio_pretty = pretty

    return


def compute_quaterly_bargraph(checks=None):
    """
    out:  []  'day':int(0-89), 'ok':int, 'ko':int, 'ratio':float|-1
    """

    WIDTH = 10

    if not checks:
        checks = get_checks()

    for check in checks:
        now = timezone.now()  # django aware (UTC with TZ convert for rendering)

        first_day = (
            now
            - timedelta(days=90)
            - timedelta(hours=now.hour)
            - timedelta(minutes=now.minute)
            - timedelta(seconds=now.second)
            - timedelta(microseconds=now.microsecond)
        )

        tmp = datetime.now()
        now_local = (
            tmp
            - timedelta(hours=tmp.hour)
            - timedelta(minutes=tmp.minute)
            - timedelta(seconds=tmp.second)
            - timedelta(microseconds=tmp.microsecond)
        )

        samples = CMTDay.objects.filter(checkname=check.keyname, timestamp__gt=first_day)

        bargraph = {}
        for i in range(90):
            bargraph[i] = {}
            bargraph[i]["OK"] = 0
            bargraph[i]["KO"] = 0
            bargraph[i]["ratio"] = -1
            bargraph[i]["tooltip"] = "NA"
            bargraph[i]["color"] = "grey"
            # x, width

        for sample in samples:
            diff = sample.timestamp - first_day
            day = 90 - diff.days

            if day < 0 or day > 89:
                continue
            if sample.count_ok:
                bargraph[day]["OK"] += sample.count_ok
            if sample.count_ko:
                bargraph[day]["KO"] += sample.count_ko

        # compute & pretty
        for i in range(90):
            #   ratio
            evtcount = bargraph[i]["OK"] + bargraph[i]["KO"]
            if evtcount > 0:
                bargraph[i]["ratio"] = bargraph[i]["OK"] / evtcount
            else:
                bargraph[i]["ratio"] = -1

            # tootlip
            tooltip_time = (now_local - timedelta(days=i)).strftime("%Y-%m-%d")
            if bargraph[i]["ratio"] >= 1:
                tooltip_ratio = "100 %"
            elif bargraph[i]["ratio"] >= 0:
                tooltip_ratio = f"{100 * bargraph[i]['ratio']:.2f} %"
            else:
                tooltip_ratio = "NA"
            bargraph[i]["tooltip"] = f"{tooltip_time} - {tooltip_ratio} - {evtcount} evt"

            #   x
            bargraph[i]["x"] = (89 - i) * WIDTH
            bargraph[i]["width"] = WIDTH - 3

            #   color
            color = "grey"
            if bargraph[i]["ratio"] >= 1:
                color = "green100"
            elif bargraph[i]["ratio"] > 0.99:
                color = "green"
            elif bargraph[i]["ratio"] > 0.98:
                color = "orange"
            elif bargraph[i]["ratio"] >= 0:
                color = "red"
            bargraph[i]["color"] = color

        check.quaterly_bargraph = bargraph

    return checks


# -----------------------------------------------
# yearly - 365D
# -----------------------------------------------
def compute_yearly_ratio(checks=None):
    """
    out:  dict {} : checkname => {'OK':int, 'KO':int, 'ratio':int}
    """
    if not checks:
        checks = get_checks()

    delta = timezone.now() - timedelta(days=365)
    samples = CMTDay.objects.filter(timestamp__gte=delta)

    table = {}
    #   compute to buckets
    for sample in samples:
        if sample.checkname not in table:
            table[sample.checkname] = {}
            table[sample.checkname]["OK"] = 0
            table[sample.checkname]["KO"] = 0
        if sample.count_ok:
            table[sample.checkname]["OK"] += sample.count_ok
        if sample.count_ko:
            table[sample.checkname]["KO"] += sample.count_ko

    #   compute ratio
    for check, _nouse in table.items():
        d = table[check]["OK"] + table[check]["KO"]
        if d > 0:
            table[check]["ratio"] = table[check]["OK"] / d
        else:
            table[check]["ratio"] = -1

    # ratio & pretty
    for check in checks:
        check.yearly_ratio = -1
        check.yearly_ratio_pretty = "NA"

        if check.keyname not in table:
            continue

        ratio = table[check.keyname]["ratio"]

        if ratio >= 1:
            pretty = "100 %"
        elif ratio >= 0:
            pretty = f"{100 * ratio:.2f} %"
        else:
            pretty = "NA"

        check.yearly_ratio = ratio
        check.yearly_ratio_pretty = pretty

    return


def compute_yearly_bargraph(checks=None):
    """Not implemented yet."""
    pass


# -----------------------------------------------
# sample RAW to HOUR
# -----------------------------------------------
def sample_hour(checks=None, start_date=None, end_date=None):
    """Aggregate CMTRaw rows in [start_date, end_date) (default: last hour to now)
    into per-hour CMTHour rows (count_ok/count_ko), replacing any existing
    CMTHour rows in that range. Returns the number of CMTHour rows written."""

    if not checks:
        checks = get_checks()

    if not start_date:
        start_date = timezone.now() - timedelta(hours=1)

    if not end_date:
        end_date = timezone.now()

    # get first/last timestamp
    first_hour = (
        start_date
        - timedelta(minutes=start_date.minute)
        - timedelta(seconds=start_date.second)
        - timedelta(microseconds=start_date.microsecond)
    )
    last_hour = (
        end_date
        - timedelta(minutes=end_date.minute)
        - timedelta(seconds=end_date.second)
        - timedelta(microseconds=end_date.microsecond)
    )

    current_hour = first_hour
    done = False
    total = 0  # samples created/updated
    while not done:
        next_hour = current_hour + timedelta(hours=1)

        for check in checks:
            count_ok = CMTRaw.objects.filter(
                checkname=check.keyname,
                timestamp__gte=current_hour,
                timestamp__lt=next_hour,
                status=True,
            ).count()

            count_ko = CMTRaw.objects.filter(
                checkname=check.keyname,
                timestamp__gte=current_hour,
                timestamp__lt=next_hour,
                status=False,
            ).count()

            # delete previous entry
            CMTHour.objects.filter(
                checkname=check.keyname, timestamp__gte=current_hour, timestamp__lt=next_hour
            ).delete()

            # create new entry
            obj = CMTHour()
            obj.timestamp = current_hour
            obj.checkname = check.keyname
            obj.count_ok = count_ok
            obj.count_ko = count_ko
            obj.save()

            total += 1

        current_hour += timedelta(hours=1)

        if current_hour > last_hour:
            done = True

    return total


# -----------------------------------------------
# sample HOUR to DAY
# -----------------------------------------------
def sample_day(checks=None, start_date=None, end_date=None):
    """Aggregate CMTHour rows in [start_date, end_date) (default: last day to now)
    into per-day CMTDay rows (count_ok/count_ko), replacing any existing
    CMTDay rows in that range. Returns the number of CMTDay rows written."""

    if not checks:
        checks = get_checks()

    if not start_date:
        start_date = timezone.now() - timedelta(days=1)

    if not end_date:
        end_date = timezone.now()

    # get first/last timestamp
    first_day = (
        start_date
        - timedelta(hours=start_date.hour)
        - timedelta(minutes=start_date.minute)
        - timedelta(seconds=start_date.second)
        - timedelta(microseconds=start_date.microsecond)
    )
    last_day = (
        end_date
        - timedelta(hours=end_date.hour)
        - timedelta(minutes=end_date.minute)
        - timedelta(seconds=end_date.second)
        - timedelta(microseconds=end_date.microsecond)
    )

    current_day = first_day
    done = False
    total = 0  # samples created/updated
    while not done:
        next_day = current_day + timedelta(days=1)

        for check in checks:
            tmp = CMTHour.objects.filter(
                checkname=check.keyname,
                timestamp__gte=current_day,
                timestamp__lt=next_day,
            ).aggregate(Sum("count_ok"))
            count_ok = tmp["count_ok__sum"]

            tmp = CMTHour.objects.filter(
                checkname=check.keyname,
                timestamp__gte=current_day,
                timestamp__lt=next_day,
            ).aggregate(Sum("count_ko"))
            count_ko = tmp["count_ko__sum"]

            # delete previous entry
            CMTDay.objects.filter(
                checkname=check.keyname, timestamp__gte=current_day, timestamp__lt=next_day
            ).delete()

            # create new entry
            obj = CMTDay()
            obj.timestamp = current_day
            obj.checkname = check.keyname
            obj.count_ok = count_ok
            obj.count_ko = count_ko
            obj.save()

            total += 1

        current_day += timedelta(days=1)

        if current_day > last_day:
            done = True

    return total


# -----------------------------------------------
# cleanup_raw per check
# -----------------------------------------------
def cleanup_raw(checks=None):
    """Delete CMTRaw rows older than CMT_RAW_KEEP_DAYS, for the given checks
    (or every CMTRaw row if checks is not provided)."""

    delete_all = False
    if not checks:
        checks = get_checks()
        delete_all = True

    keepdays = int(get_configuration("cmt", "CMT_RAW_KEEP_DAYS"))

    # delete before this timestamp
    timestamp = timezone.now() - timedelta(days=keepdays)

    # catch all if no checks provided
    if delete_all:
        CMTRaw.objects.filter(timestamp__lt=timestamp).delete()

    else:
        for check in checks:
            CMTRaw.objects.filter(checkname=check.keyname, timestamp__lt=timestamp).delete()


# -----------------------------------------------
# cleanup_hour per check
# -----------------------------------------------
def cleanup_hour(checks=None):
    """Delete CMTHour rows older than CMT_SAMPLE_HOUR_KEEP_DAYS, for the given
    checks (or every CMTHour row if checks is not provided)."""

    delete_all = False
    if not checks:
        checks = get_checks()
        delete_all = True

    keepdays = int(get_configuration("cmt", "CMT_SAMPLE_HOUR_KEEP_DAYS"))

    # delete before this timestamp
    timestamp = timezone.now() - timedelta(days=keepdays)

    # catch all if no checks provided
    if delete_all:
        CMTHour.objects.filter(timestamp__lt=timestamp).delete()

    else:
        for check in checks:
            CMTHour.objects.filter(checkname=check.keyname, timestamp__lt=timestamp).delete()


# --------------------------------------------------------
# CLASS Check
# --------------------------------------------------------
class Check:
    """In-memory view of a _cmtcheck instance plus computed display state
    (live status, ratios, bargraphs) - agent-push only, no run()/execution."""

    def __init__(self, instance=None):
        """Populate from a _cmtcheck Instance, or build an empty placeholder
        if instance is None."""

        self.keyname = ""
        self.displayname = ""
        self.is_enabled = True
        self.module = None

        # state OK/KO/NA
        self.live_status = "NA"
        # hourly
        self.hourly_ratio = None
        self.hourly_ratio_pretty = None
        self.hourly_bargraph = None  #  dict minute => bar ; bar = dict {x/ratio/count_ok/color/...}
        # daily
        self.daily_ratio = None
        self.daily_ratio_pretty = None
        self.daily_bargraph = None  #  dict hour => bar ; bar = dict {x/ratio/count_ok/color/...}
        # monthly
        self.monthly_ratio = None
        self.monthly_ratio_pretty = None
        self.monthly_bargraph = None  #  dict day => bar ; bar = dict {x/ratio/count_ok/color/...}
        # quaterly
        self.quaterly_ratio = None
        self.quaterly_ratio_pretty = None
        self.quaterly_bargraph = None  #  dict XXX => bar ; bar = dict {x/ratio/count_ok/color/...}
        # yearly
        self.yearly_ratio = None
        self.yearly_ratio_pretty = None
        self.yearly_bargraph = None  #  dict XXX => bar ; bar = dict {x/ratio/count_ok/color/...}

        # existing from DB
        if not instance:
            return

        # populate with DB instance
        self.keyname = instance.keyname
        self.displayname = instance.displayname
        self.is_enabled = instance.is_enabled

        try:
            self.module = instance.get_attribute_first("module")
        except Exception:
            self.module = None

    def __str__(self):
        return f"{self.displayname}"
