"""Spreadsheet domain services."""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Mapping, Sequence

from .entities import Sheet, Spreadsheet
from .enums import SortDirection, SyncMode
from .exceptions import DuplicateSheetError, InvalidRowError, InvalidSheetError, InvalidSpreadsheetError
from .value_objects import CellAddress, SheetName


@dataclass(frozen=True)
class SpreadsheetPolicy:
    def can_add_sheet(self, spreadsheet: Spreadsheet, sheet: Sheet) -> bool: ...
    def can_sync_rows(self, sheet: Sheet, rows: Sequence[Mapping[str, Any]], mode: SyncMode) -> bool: ...


@dataclass(frozen=True)
class DefaultSpreadsheetPolicy:
    def can_add_sheet(self, spreadsheet: Spreadsheet, sheet: Sheet) -> bool:
        if not isinstance(spreadsheet, Spreadsheet):
            raise TypeError("spreadsheet must be a Spreadsheet instance")
        if not isinstance(sheet, Sheet):
            raise TypeError("sheet must be a Sheet instance")
        return sheet.name.value not in spreadsheet.sheets

    def can_sync_rows(self, sheet: Sheet, rows: Sequence[Mapping[str, Any]], mode: SyncMode) -> bool:
        if not isinstance(sheet, Sheet):
            raise TypeError("sheet must be a Sheet instance")
        if not isinstance(mode, SyncMode):
            raise TypeError("mode must be a SyncMode instance")
        return True


@dataclass
class SpreadsheetService:
    policy: DefaultSpreadsheetPolicy = field(default_factory=DefaultSpreadsheetPolicy)

    def create(self, title: str, *, sheet_name: str = "Sheet1", headers: list[str] | None = None) -> Spreadsheet:
        spreadsheet = Spreadsheet(title=title)
        spreadsheet.add_sheet(Sheet(name=SheetName(sheet_name), headers=list(headers or [])))
        return spreadsheet

    def add_sheet(self, spreadsheet: Spreadsheet, sheet: Sheet) -> Sheet:
        if not isinstance(spreadsheet, Spreadsheet):
            raise InvalidSpreadsheetError("spreadsheet must be a Spreadsheet instance")
        if not isinstance(sheet, Sheet):
            raise InvalidSheetError("sheet must be a Sheet instance")
        if not self.policy.can_add_sheet(spreadsheet, sheet):
            raise DuplicateSheetError(f"duplicate sheet name: {sheet.name.value}")
        return spreadsheet.add_sheet(sheet)

    def get_sheet(self, spreadsheet: Spreadsheet, name: SheetName | str) -> Sheet:
        if not isinstance(spreadsheet, Spreadsheet):
            raise InvalidSpreadsheetError("spreadsheet must be a Spreadsheet instance")
        return spreadsheet.get_sheet(name)

    def append_row(self, sheet: Sheet, values: Mapping[str, Any] | Sequence[Any]) -> Sheet:
        if not isinstance(sheet, Sheet):
            raise InvalidSheetError("sheet must be a Sheet instance")
        sheet.append_row(values)
        return sheet

    def set_cell(self, sheet: Sheet, address: CellAddress | str, value: Any) -> Sheet:
        if not isinstance(sheet, Sheet):
            raise InvalidSheetError("sheet must be a Sheet instance")
        sheet.set_cell(address, value)
        return sheet

    def get_cell(self, sheet: Sheet, address: CellAddress | str) -> Any:
        if not isinstance(sheet, Sheet):
            raise InvalidSheetError("sheet must be a Sheet instance")
        return sheet.get_cell(address)

    def sync_rows(self, sheet: Sheet, rows: Sequence[Mapping[str, Any]], *, mode: SyncMode = SyncMode.APPEND) -> Sheet:
        if not isinstance(sheet, Sheet):
            raise InvalidSheetError("sheet must be a Sheet instance")
        if not isinstance(rows, Sequence):
            raise InvalidRowError("rows must be a sequence")
        if not self.policy.can_sync_rows(sheet, rows, mode):
            raise InvalidRowError("rows cannot be synced")
        if mode == SyncMode.REPLACE:
            sheet.rows.clear()
        for row in rows:
            if mode == SyncMode.UPSERT and "id" in row:
                sheet.upsert_row("id", row)
            else:
                sheet.append_row(row)
        return sheet

    def find_rows(self, sheet: Sheet, criteria: Mapping[str, Any]) -> list:
        if not isinstance(sheet, Sheet):
            raise InvalidSheetError("sheet must be a Sheet instance")
        return sheet.find_rows(criteria)

    def sort_rows(self, sheet: Sheet, key: str, direction: SortDirection = SortDirection.ASC) -> Sheet:
        if not isinstance(sheet, Sheet):
            raise InvalidSheetError("sheet must be a Sheet instance")
        sheet.sort_rows(key, direction=direction)
        return sheet
