"""WizardKit: osTicket hardware diagnostic functions"""
# vim: sts=2 sw=2 ts=2

import logging
import re

from wk import osticket
from wk.cfg.hw import (
  REGEX_BLOCK_GRAPH,
  REGEX_SMART_ATTRIBUTES,
  )
from wk.hw import smart as hw_smart
from wk.ui import cli


# STATIC VARIABLES
LOG = logging.getLogger(__name__)


# Functions
def build_report(dev, dev_type, num_disk_tests=None):
  """Build report for posting to osTicket, returns str."""
  report = []

  # Combined result
  if dev_type == 'CPU' or len(dev.tests) == num_disk_tests:
    # Build list of failed tests (if any)
    failed_tests = [t.name for t in dev.tests if t.failed]
    failed_tests = [name.replace('Disk ', '') for name in failed_tests]
    if len(failed_tests) > 2:
      failed_tests = f'{", ".join(failed_tests[:-1])}, & {failed_tests[-1]}'
    else:
      failed_tests = ' & '.join(failed_tests)

    # Get overall result
    result = 'UNKNOWN'
    if any(t.failed for t in dev.tests):
      result = 'FAILED'
    elif all(t.passed for t in dev.tests):
      result = 'PASSED'

    # Add to report
    report.append(
      f'{dev_type} hardware diagnostic tests: {result}'
      f'{" ("+failed_tests+")" if failed_tests else ""}'
      )
    report.append('')

  # Description
  if hasattr(dev, 'cpu_description'):
    report.append(dev.cpu_description)
  else:
    report.append(dev.description)
  if hasattr(dev, 'ram_total'):
    if len(dev.ram_dimms) == 1 and 'justTotalRAM' in dev.ram_dimms[0]:
      report.append(f'{dev.ram_total} (Total - no DIMM info available)')
    else:
      report.append(f'{dev.ram_total} ({", ".join(dev.ram_dimms)})')
  if hasattr(dev, 'serial') and dev.serial:
    report.append(f'Serial Number: {dev.serial}')
  report.append('')

  # Notes
  if hasattr(dev, 'notes') and dev.notes:
    report.append('Notes')
    report.extend([f'... {note}' for note in dev.notes])
    report.append('')

  # Tests
  for test in dev.tests:
    report.append(f'{test.name} ({test.status})')

    # Report
    if test.name == 'Disk Attributes' and dev.attributes:
      report.extend(
        convert_report(
          hw_smart.generate_attribute_report(dev),
          start_index=0,
          ),
        )
    else:
      report.extend(convert_report(test.report, start_index=1))

    # I/O graph upload report
    report.extend(getattr(test, 'upload_report', []))

    # Spacer
    report.append('')

  # Remove last line if empty
  if not report[-1].strip():
    report.pop(-1)

  # Done
  return cli.strip_colors('\n'.join(report))


def convert_report(original_report, start_index):
  """Convert report to an osTicket compatible type, returns list."""
  report = []

  # Convert report
  for line in original_report[start_index:]:
    # Remove colors and leading spaces
    line = cli.strip_colors(line)
    line = re.sub(r'^\s+', '', line)

    # Disk I/O Benchmark
    if REGEX_BLOCK_GRAPH.search(line):
      line = REGEX_BLOCK_GRAPH.sub('', line)
      line = line.strip()

    # SMART attributes
    match = REGEX_SMART_ATTRIBUTES.search(line)
    if match:
      # Switch decimal and hex labels
      _dec = f'{match.group("decimal"):>3}'
      _dec = osticket.pad_with_dots(_dec)
      _hex = match.group('hex')
      _data = match.group('data')
      line = f'{_hex}/{_dec}: {_data}'
      line = line.replace('failed', 'FAILED')

    # Skip empty lines
    if not line.strip():
      continue

    # Fix inner spacing
    for spacing in re.findall(r'\s\s+', line):
      new_padding = osticket.pad_with_dots(spacing)
      new_padding += ' '
      line = line.replace(spacing, new_padding)

    # Indent line
    line = f'... {line}'

    # Add to (converted) report
    report.append(line)

  # Done
  return report


def post_disk_results(state, num_disk_tests):
  """Post disk test results for all disks."""
  disk_tests = []
  for group in state.test_groups:
    if group.name.startswith('Disk'):
      disk_tests.extend(group.test_objects)

  # Bail if no disk tests were run
  if not disk_tests or state.ost.disabled:
    return

  # Post disk results
  cli.print_info('Posting results to osTicket...')
  for disk in state.disks:
    state.ost.post_response(
      build_report(disk, 'Disk', num_disk_tests),
      color='Diags FAIL' if any(t.failed for t in disk.tests) else 'Diags',
      )


def update_checkboxes(state, num_disk_tests):
  """Update osTicket checkboxes after confirmation."""
  cpu_tests = []
  disk_tests = []
  num_disk_tests_run = len(state.test_groups)

  # Build list of tests
  for group in state.test_groups:
    if group.name.startswith('CPU'):
      cpu_tests.extend(group.test_objects)
      num_disk_tests_run -= 1
    elif group.name.startswith('Disk'):
      disk_tests.extend(group.test_objects)
    elif group.name.startswith('System'):
      num_disk_tests_run -= 1

  # Bail if osTicket integration disabled
  if state.ost.disabled:
    return

  # Bail if values not confirmed
  if not cli.ask('Update osTicket checkboxes using the data above?'):
    return

  # CPU max temp and pass/fail
  if cpu_tests:
    state.ost.set_cpu_max_temp(state.sensors.get_cpu_temp('Max'))
    if any(t.failed for t in cpu_tests):
      state.ost.set_flag_failed('CPU')
    elif all(t.passed for t in cpu_tests):
      state.ost.set_flag_passed('CPU')

  # Check results for all disks
  if state.disks:
    all_disks_passed = True
    for disk in state.disks:
      if any(t.failed for t in disk.tests):
        # Mark failed disk in osTicket and stop checking results
        all_disks_passed = False
        state.ost.set_flag_failed('Disk')
        break
      if not all(t.passed for t in disk.tests):
        all_disks_passed = False
        break

    # All disks passed
    if all_disks_passed and num_disk_tests_run == num_disk_tests:
      # Only mark as passed if a full disk diagnostic passed
      state.ost.set_flag_passed('Disk')


if __name__ == '__main__':
  print("This file is not meant to be called directly.")
