"""Functions for reading and writing XLSForm Excel files.

This module provides the core library functions for converting between XLSForm
Excel files and Python dictionaries. Functions accept file-like objects (BinaryIO)
rather than filenames for maximum flexibility.
"""

import logging
from typing import BinaryIO

import openpyxl

from . import xlsform

log = logging.getLogger(__name__)


def _convert_sheet(sheet):
    """Convert an Excel sheet to a list of dictionaries.

    Args:
        sheet: openpyxl Worksheet object

    Returns:
        List of dicts, one per row (excluding header)
    """
    headers = xlsform.headers(sheet)
    result = []
    for row in xlsform.content_rows(sheet, values_only=True):
        values = xlsform.truncate_row(row)
        values = [xlsform.stringify_value(v) for v in values]
        row_dict = xlsform.row_to_dict(headers, values)
        if row_dict:
            result.append(row_dict)
    return result


def _convert_to_sheet(sheet, rows, keys):
    """Convert a list of dictionaries to an Excel sheet.

    Args:
        sheet: openpyxl Worksheet object to write to
        rows: List of dicts representing rows
        keys: List of column headers

    Returns:
        The modified sheet

    Raises:
        ValueError: If a row contains a key not in the headers list
    """
    key_set = set(keys)

    for i, key in enumerate(keys):
        sheet.cell(row=1, column=i + 1, value=key)

    next_row = 2
    previous_list_name = rows[0].get("list_name") if rows else None
    for row in rows:
        if row.get("type") in ["begin_group", "begin group"]:
            next_row += 1

        if row.get("list_name") != previous_list_name:
            previous_list_name = row.get("list_name")
            next_row += 1

        if not all(k in key_set for k in row.keys()):
            missing_key = next(k for k in row.keys() if k not in key_set)
            raise ValueError(
                f'Invalid key "{missing_key}" in row "{row.get("name", "(unnamed)")}". '
                f"Add it to yxf.headers.{sheet.title} in the YAML file."
            )

        for i, key in enumerate(keys):
            if key in row:
                sheet.cell(row=next_row, column=i + 1, value=row[key])

        next_row += 1

    return sheet


def read_xlsform(file_obj: BinaryIO) -> dict:
    """Read an XLSForm from a file-like object.

    Args:
        file_obj: Binary file-like object containing Excel data

    Returns:
        Dictionary with sheet names as keys and lists of row dicts as values.
        Also includes a "yxf" key with metadata including headers.

    Raises:
        ValueError: If the workbook doesn't have a "survey" sheet
    """
    wb = openpyxl.load_workbook(file_obj, read_only=True)
    result = {}
    headers = {}

    for sheet_name in ["survey", "choices", "settings"]:
        if sheet_name in wb:
            result[sheet_name] = _convert_sheet(wb[sheet_name])
            headers[sheet_name] = xlsform.headers(wb[sheet_name])
            if "#" in headers[sheet_name] and headers[sheet_name][0] != "#":
                raise ValueError(
                    f"The comment column must come first in sheet {sheet_name}."
                )

    if "survey" not in result:
        raise ValueError('An XLSForm must have a "survey" sheet.')

    result["yxf"] = {"headers": headers}
    return result


def write_xlsform(form: dict, file_obj: BinaryIO) -> None:
    """Write a form dictionary to an XLSForm Excel file.

    Args:
        form: Dictionary with sheet names as keys and lists of row dicts as values.
              Must include a "yxf" key with headers metadata.
        file_obj: Binary file-like object to write Excel data to

    Raises:
        ValueError: If a row contains a key not in the sheet's headers
    """
    wb = openpyxl.Workbook()
    for sheet_name in form["yxf"]["headers"]:
        _convert_to_sheet(
            wb.create_sheet(sheet_name),
            form.get(sheet_name, []),
            form["yxf"]["headers"][sheet_name],
        )
    if wb.active is not None:
        wb.remove(wb.active)
    xlsform.make_pretty(wb)
    wb.save(file_obj)
