# 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-1bot-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'})
