import logging
from datetime import datetime
from typing import (
    Generic,
    Optional,
    List,
    Dict,
    Any,
    ClassVar,
    Type,
    TypeVar,
    cast,
    TYPE_CHECKING,
)

import attr
from marshmallow import fields

from simple_smartsheet import utils
from simple_smartsheet.models import sheet as sheet_models
from simple_smartsheet.models.base import Schema, Object
from simple_smartsheet.models.cell import Cell, CellSchema
from simple_smartsheet.models.column import Column, ColumnSchema


if TYPE_CHECKING:
    try:
        import pandas as pd
    except ImportError:
        pass

logger = logging.getLogger(__name__)


class RowSchema(Schema):
    id = fields.Int()
    sheet_id = fields.Int(data_key="sheetId")
    access_level = fields.Str(data_key="accessLevel")
    attachments = fields.List(fields.Field())  # TODO: Attachment object
    conditional_format = fields.Str(data_key="conditionalFormat")
    created_at = fields.DateTime(data_key="createdAt")
    created_by = fields.Field(data_key="createdBy")  # TODO: User object
    discussions = fields.List(fields.Field())  # TODO: Discussion object
    expanded = fields.Bool()
    filtered_out = fields.Bool(data_key="filteredOut")
    format = fields.Str()
    in_critical_path = fields.Bool(data_key="inCriticalPath")
    locked = fields.Bool()
    locked_for_user = fields.Bool(data_key="lockedForUser")
    modified_at = fields.DateTime(data_key="modifiedAt")
    modified_by = fields.Field(data_key="modifiedBy")  # TODO: User object
    num = fields.Int(data_key="rowNumber")
    permalink = fields.Str()
    version = fields.Int()

    cells = fields.List(fields.Nested(CellSchema))
    columns = fields.List(fields.Nested(ColumnSchema))

    # location-specifier attributes
    parent_id = fields.Int(data_key="parentId")
    sibling_id = fields.Int(data_key="siblingId")
    above = fields.Bool()
    indent = fields.Int()
    outdent = fields.Int()
    to_bottom = fields.Bool(data_key="toBottom")
    to_top = fields.Bool(data_key="toTop")


CellT = TypeVar("CellT", bound=Cell)
RowT = TypeVar("RowT", bound="_RowBase[Any]")
ColumnT = TypeVar("ColumnT", bound=Column)


@attr.s(auto_attribs=True, repr=False, kw_only=True)
class _RowBase(Object, Generic[CellT]):
    id: Optional[int] = None
    sheet_id: Optional[int] = None
    access_level: Optional[str] = None
    attachments: List[Any] = attr.Factory(list)
    conditional_format: Optional[str] = None
    created_at: Optional[datetime] = None
    created_by: Optional[Any] = None
    discussions: List[Any] = attr.Factory(list)
    expanded: Optional[bool] = None
    filtered_out: Optional[bool] = None
    format: Optional[str] = None
    in_critical_path: Optional[bool] = None
    locked: Optional[bool] = None
    locked_for_user: Optional[bool] = None
    modified_at: Optional[datetime] = None
    modified_by: Optional[Any] = None
    num: Optional[int] = None
    permalink: Optional[str] = None
    version: Optional[int] = None

    cells: List[CellT] = attr.Factory(list)
    columns: List[Column] = attr.Factory(list)

    # location-specified attributes
    parent_id: Optional[int] = None
    sibling_id: Optional[int] = None
    above: Optional[bool] = None
    indent: Optional[int] = None
    outdent: Optional[int] = None
    to_bottom: Optional[bool] = None
    to_top: Optional[bool] = None

    # index
    column_title_to_cell: Dict[str, CellT] = attr.Factory(dict)
    column_id_to_cell: Dict[int, CellT] = attr.Factory(dict)

    _schema: ClassVar[Type[RowSchema]] = RowSchema

    def __repr__(self) -> str:
        return utils.create_repr(self, ["id", "num", "cells"])

    def _update_cell_lookup(
        self, sheet: "sheet_models._SheetBase[RowT, ColumnT]"
    ) -> None:
        self.column_title_to_cell.clear()
        self.column_id_to_cell.clear()

        for cell in self.cells:
            column_id = cell._column_id
            if column_id is None:
                continue
            column = sheet.get_column(column_id=column_id)
            column_title = column.title

            self.column_id_to_cell[column_id] = cell
            if column_title is not None:
                self.column_title_to_cell[column_title] = cell

    def _update_index(self, sheet: "sheet_models._SheetBase[RowT, ColumnT]") -> None:
        for index_key, index_dict in sheet.indexes.items():
            index = index_dict["index"]
            unique = index_dict["unique"]
            if isinstance(index_key, str):
                key = self.get_cell(index_key).value
            else:
                key = tuple(
                    self.get_cell(column_title).value for column_title in index_key
                )

            if unique:
                index[key] = self
            else:
                container = cast(List[_RowBase[CellT]], index.setdefault(key, []))
                container.append(self)

    def get_cell(
        self, column_title: Optional[str] = None, column_id: Optional[int] = None
    ) -> CellT:
        if column_title is not None:
            return self.column_title_to_cell[column_title]
        elif column_id is not None:
            return self.column_id_to_cell[column_id]
        else:
            raise ValueError(
                "Either column_title or column_id argument should be provided"
            )

    def as_dict(self) -> Dict[str, Any]:
        """Returns a dictionary of column title to cell value"""
        return {
            column_title: cell.get_value()
            for column_title, cell in self.column_title_to_cell.items()
        }

    def as_series(self) -> "pd.Series":
        """Return the row as pandas Series

        Columns will includes row id, row number and all columns from the sheet
        Pandas must be installed either separately or as extras:
          `pip install simple-smartsheet[pandas]`
        """
        import pandas as pd

        data = {"_row_id": self.id, "_row_num": self.num, **self.as_dict()}
        series = pd.Series(data)
        return series


@attr.s(auto_attribs=True, repr=False, kw_only=True)
class Row(_RowBase[Cell]):
    cells: List[Cell] = attr.Factory(list)

import logging
from datetime import datetime
from typing import (
    Generic,
    Optional,
    List,
    Dict,
    Any,
    ClassVar,
    Type,
    TypeVar,
    cast,
    TYPE_CHECKING,
)

import attr
from marshmallow import fields

from simple_smartsheet import utils
from simple_smartsheet.models import sheet as sheet_models
from simple_smartsheet.models.base import Schema, Object
from simple_smartsheet.models.cell import Cell, CellSchema
from simple_smartsheet.models.column import Column, ColumnSchema


if TYPE_CHECKING:
    try:
        import pandas as pd
    except ImportError:
        pass

logger = logging.getLogger(__name__)


class RowSchema(Schema):
    id = fields.Int()
    sheet_id = fields.Int(data_key="sheetId")
    access_level = fields.Str(data_key="accessLevel")
    attachments = fields.List(fields.Field())  # TODO: Attachment object
    conditional_format = fields.Str(data_key="conditionalFormat")
    created_at = fields.DateTime(data_key="createdAt")
    created_by = fields.Field(data_key="createdBy")  # TODO: User object
    discussions = fields.List(fields.Field())  # TODO: Discussion object
    expanded = fields.Bool()
    filtered_out = fields.Bool(data_key="filteredOut")
    format = fields.Str()
    in_critical_path = fields.Bool(data_key="inCriticalPath")
    locked = fields.Bool()
    locked_for_user = fields.Bool(data_key="lockedForUser")
    modified_at = fields.DateTime(data_key="modifiedAt")
    modified_by = fields.Field(data_key="modifiedBy")  # TODO: User object
    num = fields.Int(data_key="rowNumber")
    permalink = fields.Str()
    version = fields.Int()

    cells = fields.List(fields.Nested(CellSchema))
    columns = fields.List(fields.Nested(ColumnSchema))

    # location-specifier attributes
    parent_id = fields.Int(data_key="parentId")
    sibling_id = fields.Int(data_key="siblingId")
    above = fields.Bool()
    indent = fields.Int()
    outdent = fields.Int()
    to_bottom = fields.Bool(data_key="toBottom")
    to_top = fields.Bool(data_key="toTop")


CellT = TypeVar("CellT", bound=Cell)
RowT = TypeVar("RowT", bound="_RowBase[Any]")
ColumnT = TypeVar("ColumnT", bound=Column)


@attr.s(auto_attribs=True, repr=False, kw_only=True)
class _RowBase(Object, Generic[CellT]):
    id: Optional[int] = None
    sheet_id: Optional[int] = None
    access_level: Optional[str] = None
    attachments: List[Any] = attr.Factory(list)
    conditional_format: Optional[str] = None
    created_at: Optional[datetime] = None
    created_by: Optional[Any] = None
    discussions: List[Any] = attr.Factory(list)
    expanded: Optional[bool] = None
    filtered_out: Optional[bool] = None
    format: Optional[str] = None
    in_critical_path: Optional[bool] = None
    locked: Optional[bool] = None
    locked_for_user: Optional[bool] = None
    modified_at: Optional[datetime] = None
    modified_by: Optional[Any] = None
    num: Optional[int] = None
    permalink: Optional[str] = None
    version: Optional[int] = None

    cells: List[CellT] = attr.Factory(list)
    columns: List[Column] = attr.Factory(list)

    # location-specified attributes
    parent_id: Optional[int] = None
    sibling_id: Optional[int] = None
    above: Optional[bool] = None
    indent: Optional[int] = None
    outdent: Optional[int] = None
    to_bottom: Optional[bool] = None
    to_top: Optional[bool] = None

    # index
    column_title_to_cell: Dict[str, CellT] = attr.Factory(dict)
    column_id_to_cell: Dict[int, CellT] = attr.Factory(dict)

    _schema: ClassVar[Type[RowSchema]] = RowSchema

    def __repr__(self) -> str:
        return utils.create_repr(self, ["id", "num", "cells"])

    def _update_cell_lookup(
        self, sheet: "sheet_models._SheetBase[RowT, ColumnT]"
    ) -> None:
        self.column_title_to_cell.clear()
        self.column_id_to_cell.clear()

        for cell in self.cells:
            column_id = cell._column_id
            if column_id is None:
                continue
            column = sheet.get_column(column_id=column_id)
            column_title = column.title

            self.column_id_to_cell[column_id] = cell
            if column_title is not None:
                self.column_title_to_cell[column_title] = cell

    def _update_index(self, sheet: "sheet_models._SheetBase[RowT, ColumnT]") -> None:
        for index_key, index_dict in sheet.indexes.items():
            index = index_dict["index"]
            unique = index_dict["unique"]
            if isinstance(index_key, str):
                key = self.get_cell(index_key).value
            else:
                key = tuple(
                    self.get_cell(column_title).value for column_title in index_key
                )

            if unique:
                index[key] = self
            else:
                container = cast(List[_RowBase[CellT]], index.setdefault(key, []))
                container.append(self)

    def get_cell(
        self, column_title: Optional[str] = None, column_id: Optional[int] = None
    ) -> CellT:
        if column_title is not None:
            return self.column_title_to_cell[column_title]
        elif column_id is not None:
            return self.column_id_to_cell[column_id]
        else:
            raise ValueError(
                "Either column_title or column_id argument should be provided"
            )

    def as_dict(self) -> Dict[str, Any]:
        """Returns a dictionary of column title to cell value"""
        return {
            column_title: cell.get_value()
            for column_title, cell in self.column_title_to_cell.items()
        }

    def as_series(self) -> "pd.Series":
        """Return the row as pandas Series

        Columns will includes row id, row number and all columns from the sheet
        Pandas must be installed either separately or as extras:
          `pip install simple-smartsheet[pandas]`
        """
        import pandas as pd

        data = {"_row_id": self.id, "_row_num": self.num, **self.as_dict()}
        series = pd.Series(data)
        return series


@attr.s(auto_attribs=True, repr=False, kw_only=True)
class Row(_RowBase[Cell]):
    cells: List[Cell] = attr.Factory(list)

# TODO: Attachment object # TODO: User object # TODO: Discussion object # TODO: User object # location-specifier attributes # location-specified attributes # index Returns a dictionary of column title to cell value Return the row as pandas Series          Columns will includes row id, row number and all columns from the sheet         Pandas must be installed either separately or as extras:           `pip install simple-smartsheet[pandas]`