# Copyright 2021 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
"""Tests for pw_console.text_formatting"""

from datetime import datetime
import logging
import unittest

from parameterized import parameterized  # type: ignore

from pw_console.console_prefs import ConsolePrefs
from pw_console.log_line import LogLine
from pw_console.widgets.table import TableView

_TIMESTAMP_FORMAT = '%Y%m%d %H:%M:%S'
_TIMESTAMP_SAMPLE = datetime(2021, 6, 30, 16, 10, 37, 818901)
_TIMESTAMP_SAMPLE_STRING = _TIMESTAMP_SAMPLE.strftime(_TIMESTAMP_FORMAT)

_TABLE_PADDING = '  '
_TABLE_PADDING_FRAGMENT = ('', _TABLE_PADDING)

formatter = logging.Formatter(
    '\x1b[30m\x1b[47m'
    '%(asctime)s'
    '\x1b[0m'
    ' '
    '\x1b[33m\x1b[1m'
    '%(levelname)s'
    '\x1b[0m'
    ' '
    '%(message)s',
    _TIMESTAMP_FORMAT,
)


def make_log(**kwargs):
    """Create a LogLine instance."""
    # Construct a LogRecord
    attributes = dict(
        name='testlogger',
        levelno=logging.INFO,
        levelname='INF',
        msg='[%s] %.3f %s',
        args=('MOD1', 3.14159, 'Real message here'),
        created=_TIMESTAMP_SAMPLE.timestamp(),
        filename='test.py',
        lineno=42,
        pathname='/home/user/test.py',
    )
    # Override any above attrs that are passed in.
    attributes.update(kwargs)
    # Create the record
    record = logging.makeLogRecord(dict(attributes))
    # Format
    formatted_message = formatter.format(record)
    log_line = LogLine(
        record=record, formatted_log=formatted_message, ansi_stripped_log=''
    )
    log_line.update_metadata()
    return log_line


class TestTableView(unittest.TestCase):
    """Tests for rendering log lines into tables."""

    maxDiff = None

    def setUp(self):
        # Show large diffs
        self.prefs = ConsolePrefs(
            project_file=False, project_user_file=False, user_file=False
        )
        self.prefs.reset_config()

    @parameterized.expand(
        [
            (
                'Correct column widths with all fields set',
                [
                    make_log(
                        args=('M1', 1.2345, 'Something happened'),
                        extra_metadata_fields=dict(module='M1', anumber=12),
                    ),
                    make_log(
                        args=('MD2', 567.5, 'Another cool event'),
                        extra_metadata_fields=dict(module='MD2', anumber=123),
                    ),
                ],
                dict(module=len('MD2'), anumber=len('123')),
            ),
            (
                'Missing metadata fields on some rows',
                [
                    make_log(
                        args=('M1', 54321.2, 'Something happened'),
                        extra_metadata_fields=dict(
                            module='M1', anumber=54321.2
                        ),
                    ),
                    make_log(
                        args=('MOD2', 567.5, 'Another cool event'),
                        extra_metadata_fields=dict(module='MOD2'),
                    ),
                ],
                dict(module=len('MOD2'), anumber=len('54321.200')),
            ),
        ]
    )
    def test_column_widths(self, _name, logs, expected_widths) -> None:
        """Test colum widths calculation."""
        table = TableView(self.prefs)
        for log in logs:
            table.update_metadata_column_widths(log)
            metadata_fields = {
                k: v
                for k, v in log.metadata.fields.items()
                if k not in ['py_file', 'py_logger']
            }
            # update_metadata_column_widths shoulp populate self.metadata.fields
            self.assertEqual(metadata_fields, log.record.extra_metadata_fields)
        # Check expected column widths
        results = {
            k: v
            for k, v in dict(table.column_widths).items()
            if k not in ['time', 'level', 'py_file', 'py_logger']
        }
        self.assertCountEqual(expected_widths, results)

    @parameterized.expand(
        [
            (
                'Build header adding fields incrementally',
                [
                    make_log(
                        args=('MODULE2', 567.5, 'Another cool event'),
                        extra_metadata_fields=dict(
                            # timestamp missing
                            module='MODULE2'
                        ),
                    ),
                    make_log(
                        args=('MODULE1', 54321.2, 'Something happened'),
                        extra_metadata_fields=dict(
                            # timestamp added in
                            module='MODULE1',
                            timestamp=54321.2,
                        ),
                    ),
                ],
                [
                    [
                        ('bold', 'Time             '),
                        _TABLE_PADDING_FRAGMENT,
                        ('bold', 'Lev'),
                        _TABLE_PADDING_FRAGMENT,
                        ('bold', 'Module '),
                        _TABLE_PADDING_FRAGMENT,
                        ('bold', 'Message'),
                    ],
                    [
                        ('bold', 'Time             '),
                        _TABLE_PADDING_FRAGMENT,
                        ('bold', 'Lev'),
                        _TABLE_PADDING_FRAGMENT,
                        ('bold', 'Module '),
                        _TABLE_PADDING_FRAGMENT,
                        # timestamp added in
                        ('bold', 'Timestamp'),
                        _TABLE_PADDING_FRAGMENT,
                        ('bold', 'Message'),
                    ],
                ],
            ),
        ]
    )
    def test_formatted_header(self, _name, logs, expected_headers) -> None:
        """Test colum widths calculation."""
        table = TableView(self.prefs)

        for log, header in zip(logs, expected_headers):
            table.update_metadata_column_widths(log)
            self.assertEqual(table.formatted_header(), header)

    @parameterized.expand(
        [
            (
                'Build rows adding fields incrementally',
                [
                    make_log(
                        args=('MODULE2', 567.5, 'Another cool event'),
                        extra_metadata_fields=dict(
                            # timestamp missing
                            module='MODULE2',
                            msg='Another cool event',
                        ),
                    ),
                    make_log(
                        args=('MODULE2', 567.5, 'Another cool event'),
                        extra_metadata_fields=dict(
                            # timestamp and msg missing
                            module='MODULE2'
                        ),
                    ),
                    make_log(
                        args=('MODULE1', 54321.2, 'Something happened'),
                        extra_metadata_fields=dict(
                            # timestamp added in
                            module='MODULE1',
                            timestamp=54321.2,
                            msg='Something happened',
                        ),
                    ),
                ],
                [
                    [
                        ('class:log-time', _TIMESTAMP_SAMPLE_STRING),
                        _TABLE_PADDING_FRAGMENT,
                        ('class:log-level-20', 'INF'),
                        _TABLE_PADDING_FRAGMENT,
                        ('class:log-table-column-0', 'MODULE2'),
                        _TABLE_PADDING_FRAGMENT,
                        ('', 'Another cool event'),
                        ('', '\n'),
                    ],
                    [
                        ('class:log-time', _TIMESTAMP_SAMPLE_STRING),
                        _TABLE_PADDING_FRAGMENT,
                        ('class:log-level-20', 'INF'),
                        _TABLE_PADDING_FRAGMENT,
                        ('class:log-table-column-0', 'MODULE2'),
                        _TABLE_PADDING_FRAGMENT,
                        ('', '[MODULE2] 567.500 Another cool event'),
                        ('', '\n'),
                    ],
                    [
                        ('class:log-time', _TIMESTAMP_SAMPLE_STRING),
                        _TABLE_PADDING_FRAGMENT,
                        ('class:log-level-20', 'INF'),
                        _TABLE_PADDING_FRAGMENT,
                        ('class:log-table-column-0', 'MODULE1'),
                        _TABLE_PADDING_FRAGMENT,
                        ('class:log-table-column-1', '54321.200'),
                        _TABLE_PADDING_FRAGMENT,
                        ('', 'Something happened'),
                        ('', '\n'),
                    ],
                ],
            ),
        ]
    )
    def test_formatted_rows(self, _name, logs, expected_log_format) -> None:
        """Test colum widths calculation."""
        table = TableView(self.prefs)
        # Check each row meets expected formats incrementally.
        for log, formatted_log in zip(logs, expected_log_format):
            table.update_metadata_column_widths(log)
            self.assertEqual(formatted_log, table.formatted_row(log))


if __name__ == '__main__':
    unittest.main()
