"""Original InternshipDeadlines synthetic defensive exercise. Python 3.10+.
Run: python triage_case.py
No networking, credentials, real logs or account changes. An alert means review,
not compromise. Familiar sources are supplied fixture assumptions.
"""
from collections import defaultdict
from datetime import datetime, timedelta
import json
import unittest

EVENTS = [
 {'account':'A','at':'2026-01-01T09:00:00+00:00','outcome':'failure','source':'office'},
 {'account':'A','at':'2026-01-01T09:01:00+00:00','outcome':'failure','source':'office'},
 {'account':'A','at':'2026-01-01T09:02:00+00:00','outcome':'failure','source':'office'},
 {'account':'A','at':'2026-01-01T09:03:00+00:00','outcome':'failure','source':'office'},
 {'account':'A','at':'2026-01-01T09:04:00+00:00','outcome':'success','source':'office'},
 {'account':'B','at':'2026-01-01T09:05:00+00:00','outcome':'success','source':'unfamiliar'},
 {'account':'C','at':'2026-01-01T09:06:00+00:00','outcome':'failure','source':'office'}
]

def triage(events, familiar, minutes=10, threshold=4):
    if type(minutes) is not int or minutes < 1 or type(threshold) is not int or threshold < 1:
        raise ValueError('Window and threshold must be positive integers')
    grouped = defaultdict(list)
    for event in events:
        if set(event) != {'account','at','outcome','source'}:
            raise ValueError('Unexpected or missing event fields')
        if not all(isinstance(v,str) and v for v in event.values()):
            raise ValueError('Event fields must be nonempty strings')
        if event['outcome'] not in ('failure','success'):
            raise ValueError('Unknown outcome')
        time = datetime.fromisoformat(event['at'])
        if time.utcoffset() is None: raise ValueError('Timestamp must have a timezone')
        grouped[event['account']].append((time,event))
    notes = []
    for account, rows in sorted(grouped.items()):
        rows.sort(key=lambda item:item[0])
        for time, event in rows:
            if event['outcome'] != 'success': continue
            failures = [e['at'] for t,e in rows
                        if time-timedelta(minutes=minutes) <= t < time and e['outcome']=='failure']
            if len(failures) >= threshold:
                notes.append({'account':account,'at':event['at'],'rule':'failures_then_success',
                  'evidence':failures+[event['at']],'disposition':'review',
                  'next_question':'Check authentication context and whether activity was expected'})
            if event['source'] not in familiar.get(account,set()):
                notes.append({'account':account,'at':event['at'],'rule':'unfamiliar_success_source',
                  'evidence':[event['source']],'disposition':'review',
                  'next_question':'Verify source familiarity and device continuity through the approved process'})
    return notes

class TriageTests(unittest.TestCase):
    def test_sample_rules(self):
        notes = triage(EVENTS,{'A':{'office'},'B':{'office'},'C':{'office'}})
        self.assertEqual([(n['account'],n['rule']) for n in notes],
          [('A','failures_then_success'),('B','unfamiliar_success_source')])
    def test_outside_window(self):
        events = [dict(e) for e in EVENTS[:5]]
        events[-1]['at']='2026-01-01T09:14:00+00:00'
        self.assertEqual(triage(events,{'A':{'office'}}),[])
    def test_boundary_is_inclusive(self):
        events=[dict(e) for e in EVENTS[:5]]
        events[-1]['at']='2026-01-01T09:10:00+00:00'
        self.assertEqual(len(triage(events,{'A':{'office'}})),1)
    def test_input_order_does_not_change_result(self):
        known={'A':{'office'},'B':{'office'}}
        self.assertEqual(triage(EVENTS,known),triage(list(reversed(EVENTS)),known))
    def test_naive_time_is_rejected(self):
        event=dict(EVENTS[0],at='2026-01-01T09:00:00')
        with self.assertRaises(ValueError): triage([event],{})
    def test_missing_field_and_invalid_threshold(self):
        with self.assertRaises(ValueError): triage([{'account':'A'}],{})
        with self.assertRaises(ValueError): triage([],{},threshold=0)

if __name__ == '__main__':
    print(json.dumps(triage(EVENTS,{'A':{'office'},'B':{'office'},'C':{'office'}}),indent=2))
    print('Limitations: supplied source familiarity; complete logs not established; duplicate events need upstream identifiers.')
    result=unittest.TextTestRunner(verbosity=2).run(unittest.defaultTestLoader.loadTestsFromTestCase(TriageTests))
    raise SystemExit(0 if result.wasSuccessful() else 1)
