# Copyright 2024 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import datetime
import typing
from typing import Union
import unittest
from unittest import mock

from blinkpy.w3c import buganizer as blink_buganizer

from bad_machine_finder import buganizer
from bad_machine_finder import detection

# pylint: disable=protected-access


class FakeBuganizerClient:

  def __init__(self):
    # GetIssueComments
    self.issue_comments = []

    # NewComment
    self.comment_content = None

  def GetIssueComments(self, bug_id: int) -> Union[dict, list]:
    del bug_id  # unused.
    return self.issue_comments

  def NewComment(self, bug_id: int, comment: str, use_markdown: bool) -> None:
    del bug_id, use_markdown  # unused.
    self.comment_content = comment


def _GetIsoFormatStringForNDaysAgo(num_days: int) -> str:
  now = datetime.datetime.now()
  n_days_ago = now - datetime.timedelta(days=num_days)
  return n_days_ago.isoformat()


class UpdateBugUnittest(unittest.TestCase):

  def testBasic(self):
    """Tests the basic behavior of posting an update to a bug."""
    client = FakeBuganizerClient()
    client.issue_comments = [
        {
            'timestamp':
            _GetIsoFormatStringForNDaysAgo(1),
            'comment':
            '\n'.join([
                buganizer._AUTOMATED_COMMENT_START,
                'bot-2',
                'bot-3',
            ]),
        },
    ]

    first_machine_list = detection.BadMachineList()
    first_machine_list.AddBadMachine('bot-1', 'reason-1a')
    first_machine_list.AddBadMachine('bot-1', 'reason-1b')
    first_machine_list.AddBadMachine('bot-2', 'reason-2')

    second_machine_list = detection.BadMachineList()
    second_machine_list.AddBadMachine('bot-3', 'reason-3')
    second_machine_list.AddBadMachine('bot-4', 'reason-4')
    second_machine_list.AddBadMachine('bot-5', 'reason-5')

    mgbm = detection.MixinGroupedBadMachines()
    mgbm.AddMixinData('mixin-a', first_machine_list)
    mgbm.AddMixinData('mixin-b', second_machine_list)

    with mock.patch.object(buganizer,
                           '_GetBuganizerClient',
                           return_value=client):
      buganizer.UpdateBug(1234, mgbm, 7)

    expected_markdown = f"""\
{buganizer._AUTOMATED_COMMENT_START}

Bad machines for mixin-a
  * bot-1
    * reason-1a
    * reason-1b

Bad machines for mixin-b
  * bot-4
    * reason-4
  * bot-5
    * reason-5"""
    self.assertEqual(client.comment_content, expected_markdown)

  def testNoNewBadMachines(self):
    """Tests behavior when all bad machines were recently reported."""
    client = FakeBuganizerClient()
    client.issue_comments = [
        {
            'timestamp':
            _GetIsoFormatStringForNDaysAgo(1),
            'comment':
            '\n'.join([
                buganizer._AUTOMATED_COMMENT_START,
                'bot-1'
                'bot-2',
                'bot-3',
                'bot-4',
                'bot-5',
            ]),
        },
    ]

    first_machine_list = detection.BadMachineList()
    first_machine_list.AddBadMachine('bot-1', 'reason-1a')
    first_machine_list.AddBadMachine('bot-1', 'reason-1b')
    first_machine_list.AddBadMachine('bot-2', 'reason-2')

    second_machine_list = detection.BadMachineList()
    second_machine_list.AddBadMachine('bot-3', 'reason-3')
    second_machine_list.AddBadMachine('bot-4', 'reason-4')
    second_machine_list.AddBadMachine('bot-5', 'reason-5')

    mgbm = detection.MixinGroupedBadMachines()
    mgbm.AddMixinData('mixin-a', first_machine_list)
    mgbm.AddMixinData('mixin-b', second_machine_list)

    with mock.patch.object(buganizer,
                           '_GetBuganizerClient',
                           return_value=client):
      buganizer.UpdateBug(1234, mgbm, 7)

    expected_markdown = f"""\
{buganizer._AUTOMATED_COMMENT_START}

No new bad machines detected"""
    self.assertEqual(client.comment_content, expected_markdown)


class GetRecentlyReportedBotsUnittest(unittest.TestCase):

  def testBugNotAccessible(self):
    """Tests behavior when accessing the bug returns an error."""
    client = typing.cast(blink_buganizer.BuganizerClient, FakeBuganizerClient())
    client.issue_comments = {'error': 'error_message'}

    with self.assertRaisesRegex(
        buganizer.BugNotAccessibleException,
        'Failed to get comments from 1234: error_message'):
      buganizer._GetRecentlyReportedBots(1234, client, [], 7)

  def testBasic(self):
    """Tests the basic behavior of finding recently reported bots."""
    client = typing.cast(blink_buganizer.BuganizerClient, FakeBuganizerClient())
    client.issue_comments = [
        # Should be ignored because it's not an automated comment.
        {
            'timestamp': _GetIsoFormatStringForNDaysAgo(1),
            'comment': '\n'.join([
                'bot-1',
                'bot-2',
                'bot-3',
                'bot-4',
            ]),
        },
        # Should be ignored because it's too old.
        {
            'timestamp':
            _GetIsoFormatStringForNDaysAgo(14),
            'comment':
            '\n'.join([
                buganizer._AUTOMATED_COMMENT_START,
                'bot-1',
                'bot-2',
                'bot-3',
                'bot-4',
            ]),
        },
        # Should be parsed.
        {
            'timestamp':
            _GetIsoFormatStringForNDaysAgo(1),
            'comment':
            '\n'.join([
                buganizer._AUTOMATED_COMMENT_START,
                'bot-2',
                'bot-3',
                'bot-5',
            ]),
        },
    ]

    recently_reported_bots = buganizer._GetRecentlyReportedBots(
        1234, client, {'bot-1', 'bot-2', 'bot-3', 'bot-4'}, 7)
    self.assertEqual(recently_reported_bots, {'bot-2', 'bot-3'})

  def testIsoZCompatibility(self):
    """Tests that a trailing Z in an ISO 8601 string does not cause issues."""
    client = typing.cast(blink_buganizer.BuganizerClient, FakeBuganizerClient())
    client.issue_comments = [
        {
            'timestamp':
            _GetIsoFormatStringForNDaysAgo(1) + 'Z',
            'comment':
            '\n'.join([
                buganizer._AUTOMATED_COMMENT_START,
                'bot-2',
                'bot-3',
                'bot-5',
            ]),
        },
    ]

    recently_reported_bots = buganizer._GetRecentlyReportedBots(
        1234, client, {'bot-1', 'bot-2', 'bot-3', 'bot-4'}, 7)
    self.assertEqual(recently_reported_bots, {'bot-2', 'bot-3'})
