#  Copyright © 2022 CloudBlue. All rights reserved.

import inspect
import json
import os
from datetime import datetime

import pytz

from openpyxl import load_workbook
from openpyxl.styles import Alignment, Font, PatternFill
from openpyxl.styles.colors import Color, WHITE
from openpyxl.utils.exceptions import InvalidFileException

from zipfile import BadZipfile

from connect.reports.renderers.base import BaseRenderer
from connect.reports.renderers.registry import register
from connect.reports.renderers.utils import aiter


@register('xlsx')
class XLSXRenderer(BaseRenderer):
    """
    XLSX Renderer class.
    Inherits from BaseRenderer class and implements
    the generation report function, exporting the data
    to a Excel file.
    """
    def render(self, data, output_file, start_time=None):
        self.start_time = start_time or datetime.now(tz=pytz.utc)
        return self.generate_report(data, output_file)

    async def render_async(self, data, output_file, start_time=None):
        self.start_time = start_time or datetime.now(tz=pytz.utc)
        return await self.generate_report_async(data, output_file)

    def generate_report(self, data, output_file):
        start_col_idx = self.args.get('start_col', 1)
        row_idx = self.args.get('start_row', 2)
        wb = load_workbook(
            os.path.join(
                self.root_dir,
                self.template,
            ),
        )
        ws = wb['Data']
        for row in data:
            for col_idx, cell_value in enumerate(row, start=start_col_idx):
                ws.cell(row_idx, col_idx, value=cell_value)
            row_idx += 1

        self._add_info_sheet(wb.create_sheet('Info'), self.start_time)

        output_file = f'{output_file}.xlsx'
        wb.save(output_file)
        return output_file

    async def generate_report_async(self, data, output_file):
        start_col_idx = self.args.get('start_col', 1)
        row_idx = self.args.get('start_row', 2)
        wb = await self._to_thread(
            load_workbook,
            os.path.join(
                self.root_dir,
                self.template,
            ),
        )
        ws = wb['Data']
        if not inspect.isasyncgen(data):
            data = aiter(data)
        async for row in data:
            for col_idx, cell_value in enumerate(row, start=start_col_idx):
                ws.cell(row_idx, col_idx, value=cell_value)
            row_idx += 1

        self._add_info_sheet(wb.create_sheet('Info'), self.start_time)

        output_file = f'{output_file}.xlsx'
        await self._to_thread(wb.save, output_file)
        return output_file

    def _add_info_sheet(self, ws, start_time):
        ws.column_dimensions['A'].width = 50
        ws.column_dimensions['B'].width = 180
        ws['A1'].value = 'Report Execution Information'
        ws.merge_cells('A1:B1')
        ws['A1'].fill = PatternFill('solid', start_color=Color('1565C0'))
        ws['A1'].font = Font(sz=24, color=WHITE)
        ws['A1'].alignment = Alignment(horizontal='center', vertical='center')
        for i in range(2, 10):
            ws[f'A{i}'].alignment = Alignment(
                horizontal='left',
                vertical='top',
            )
            ws[f'B{i}'].alignment = Alignment(
                horizontal='left',
                vertical='top',
            )
        ws['A2'].value = 'Report Start time'
        ws['B2'].value = start_time.strftime('%Y-%m-%d %H:%M:%S')
        ws['A3'].value = 'Report Finish time'
        ws['B3'].value = datetime.now(tz=pytz.utc).strftime('%Y-%m-%d %H:%M:%S')
        ws['A4'].value = 'Account ID'
        ws['B4'].value = self.account.id
        ws['A5'].value = 'Account Name'
        ws['B5'].value = self.account.name
        ws['A6'].value = 'Report ID'
        ws['B6'].value = self.report.id
        ws['A7'].value = 'Report Name'
        ws['B7'].value = self.report.name
        ws['A8'].value = 'Runtime environment'
        ws['B8'].value = self.environment
        ws['A9'].value = 'Report execution parameters'
        ws['B9'].value = json.dumps(self.report.values, indent=4, sort_keys=True)
        ws['B9'].alignment = Alignment(
            horizontal='left',
            vertical='top',
            wrap_text=True,
        )

    @classmethod
    def _validate_args(cls, args):
        errors = []
        start_row = args.get('start_row')
        start_col = args.get('start_col')
        if start_row is not None:
            if not isinstance(start_row, int):
                errors.append('`start_row` must be integer.')
            else:
                if start_row < 1:
                    errors.append('`start_row` must be greater than 0.')
        if start_col is not None:
            if not isinstance(start_col, int):
                errors.append('`start_col` must be integer.')
            else:
                if start_col < 1:
                    errors.append('`start_col` must be greater than 0.')
        return errors

    @classmethod
    def validate(cls, definition):
        errors = []
        if definition.template is None:
            errors.append('`template` is required for xlsx renderer.')
            return errors
        elif not os.path.isfile(
            os.path.join(definition.root_path, definition.template),
        ):
            errors.append(f'template `{definition.template}` not found.')
            return errors

        try:
            template_file = os.path.join(definition.root_path, definition.template)
            load_workbook(template_file)
        except (InvalidFileException, BadZipfile):
            errors.append(f'template `{definition.template}` not valid or empty.')

        if definition.args is not None:
            errors.extend(cls._validate_args(definition.args))
        return errors
