"""SQLAlchemy implementation of WorkspaceRepository."""

from __future__ import annotations

from collections.abc import Sequence
from typing import cast
from uuid import UUID

from sqlalchemy import and_, select

from noteflow.config.constants import (
    RULE_FIELD_APP_MATCH_PATTERNS,
    RULE_FIELD_AUTO_START_ENABLED,
    RULE_FIELD_CALENDAR_MATCH_PATTERNS,
    RULE_FIELD_DEFAULT_FORMAT,
    RULE_FIELD_EXPORT_RULES,
    RULE_FIELD_INCLUDE_AUDIO,
    RULE_FIELD_INCLUDE_TIMESTAMPS,
    RULE_FIELD_TEMPLATE_ID,
    RULE_FIELD_TRIGGER_RULES,
)
from noteflow.domain.entities.project import ExportRules, TriggerRules
from noteflow.domain.identity import (
    Workspace,
    WorkspaceMembership,
    WorkspaceRole,
    WorkspaceSettings,
)
from noteflow.domain.value_objects import ExportFormat
from noteflow.infrastructure.persistence.models import (
    DEFAULT_WORKSPACE_ID,
    WorkspaceMembershipModel,
    WorkspaceModel,
)
from noteflow.infrastructure.persistence.repositories._base import (
    BaseRepository,
    DeleteByIdMixin,
    GetByIdMixin,
    bool_or_none,
    string_list_or_none,
)


def _uuid_or_none(value: object) -> UUID | None:
    if isinstance(value, UUID):
        return value
    if isinstance(value, str):
        try:
            return UUID(value)
        except ValueError:
            return None
    return None


class SqlAlchemyWorkspaceRepository(
    BaseRepository,
    GetByIdMixin[WorkspaceModel, Workspace],
    DeleteByIdMixin[WorkspaceModel],
):
    """SQLAlchemy implementation of WorkspaceRepository.

    Manage workspaces for multi-tenant resource organization.
    Uses mixins for standardized get and delete operations.
    """

    _model_class: type[WorkspaceModel] = WorkspaceModel

    @staticmethod
    def _settings_from_dict(data: dict[str, object] | None) -> WorkspaceSettings:
        """Convert JSONB dict to WorkspaceSettings.

        Args:
            data: JSONB dictionary from database.

        Returns:
            WorkspaceSettings with parsed values.
        """
        if not data:
            return WorkspaceSettings()

        export_rules = None
        raw_export_data = data.get(RULE_FIELD_EXPORT_RULES)
        if isinstance(raw_export_data, dict):
            export_data = cast(dict[str, object], raw_export_data)
            format_str = export_data.get(RULE_FIELD_DEFAULT_FORMAT)
            default_format = ExportFormat(format_str) if isinstance(format_str, str) else None
            export_rules = ExportRules(
                default_format=default_format,
                include_audio=bool_or_none(export_data.get(RULE_FIELD_INCLUDE_AUDIO)),
                include_timestamps=bool_or_none(export_data.get(RULE_FIELD_INCLUDE_TIMESTAMPS)),
                template_id=_uuid_or_none(export_data.get(RULE_FIELD_TEMPLATE_ID)),
            )

        trigger_rules = None
        raw_trigger_data = data.get(RULE_FIELD_TRIGGER_RULES)
        if isinstance(raw_trigger_data, dict):
            trigger_data = cast(dict[str, object], raw_trigger_data)
            trigger_rules = TriggerRules(
                auto_start_enabled=bool_or_none(trigger_data.get(RULE_FIELD_AUTO_START_ENABLED)),
                calendar_match_patterns=string_list_or_none(
                    trigger_data.get(RULE_FIELD_CALENDAR_MATCH_PATTERNS),
                ),
                app_match_patterns=string_list_or_none(
                    trigger_data.get(RULE_FIELD_APP_MATCH_PATTERNS),
                ),
            )

        # Extract and validate optional settings with type narrowing
        rag_enabled_raw = data.get("rag_enabled")
        rag_enabled = rag_enabled_raw if isinstance(rag_enabled_raw, bool) else None

        template_raw = data.get("default_summarization_template")
        template = template_raw if isinstance(template_raw, str) else None

        return WorkspaceSettings(
            export_rules=export_rules,
            trigger_rules=trigger_rules,
            rag_enabled=rag_enabled,
            default_summarization_template=template,
        )

    @staticmethod
    def _settings_to_dict(settings: WorkspaceSettings) -> dict[str, object]:
        """Convert WorkspaceSettings to JSONB dict.

        Args:
            settings: WorkspaceSettings to convert.

        Returns:
            Dictionary for JSONB storage.
        """
        data: dict[str, object] = {}

        if settings.export_rules:
            data[RULE_FIELD_EXPORT_RULES] = {
                RULE_FIELD_DEFAULT_FORMAT: settings.export_rules.default_format.value
                if settings.export_rules.default_format
                else None,
                RULE_FIELD_INCLUDE_AUDIO: settings.export_rules.include_audio,
                RULE_FIELD_INCLUDE_TIMESTAMPS: settings.export_rules.include_timestamps,
                RULE_FIELD_TEMPLATE_ID: settings.export_rules.template_id,
            }

        if settings.trigger_rules:
            data[RULE_FIELD_TRIGGER_RULES] = {
                RULE_FIELD_AUTO_START_ENABLED: settings.trigger_rules.auto_start_enabled,
                RULE_FIELD_CALENDAR_MATCH_PATTERNS: settings.trigger_rules.calendar_match_patterns,
                RULE_FIELD_APP_MATCH_PATTERNS: settings.trigger_rules.app_match_patterns,
            }

        if settings.rag_enabled is not None:
            data["rag_enabled"] = settings.rag_enabled

        if settings.default_summarization_template is not None:
            data["default_summarization_template"] = settings.default_summarization_template

        return data

    def _to_domain(self, model: WorkspaceModel) -> Workspace:
        """Convert ORM model to domain entity.

        Args:
            model: SQLAlchemy WorkspaceModel.

        Returns:
            Domain Workspace entity.
        """
        return Workspace(
            id=model.id,
            name=model.name,
            slug=model.slug,
            is_default=model.is_default,
            settings=self._settings_from_dict(model.settings),
            created_at=model.created_at,
            updated_at=model.updated_at,
            metadata=dict(model.metadata_) if model.metadata_ else {},
        )

    @staticmethod
    def _membership_to_domain(model: WorkspaceMembershipModel) -> WorkspaceMembership:
        """Convert ORM membership model to domain entity.

        Args:
            model: SQLAlchemy WorkspaceMembershipModel.

        Returns:
            Domain WorkspaceMembership entity.
        """
        return WorkspaceMembership(
            workspace_id=model.workspace_id,
            user_id=model.user_id,
            role=WorkspaceRole(model.role),
            created_at=model.created_at,
        )

    async def get(self, workspace_id: UUID) -> Workspace | None:
        """Get workspace by ID.

        Args:
            workspace_id: Workspace UUID.

        Returns:
            Workspace if found, None otherwise.
        """
        return await self._mixin_get_by_id(workspace_id)

    async def get_by_slug(self, slug: str) -> Workspace | None:
        """Get workspace by slug.

        Args:
            slug: Workspace slug.

        Returns:
            Workspace if found, None otherwise.
        """
        stmt = select(WorkspaceModel).where(WorkspaceModel.slug == slug)
        model = await self._execute_scalar(stmt)
        return self._to_domain(model) if model else None

    async def get_default_for_user(self, user_id: UUID) -> Workspace | None:
        """Get the default workspace for a user.

        In local-first mode, returns the default workspace if the user
        is a member of it.

        Args:
            user_id: User UUID.

        Returns:
            Default workspace if exists, None otherwise.
        """
        default_id = UUID(DEFAULT_WORKSPACE_ID)

        # Check if user is a member of the default workspace
        membership_stmt = select(WorkspaceMembershipModel).where(
            and_(
                WorkspaceMembershipModel.workspace_id == default_id,
                WorkspaceMembershipModel.user_id == user_id,
            ),
        )
        membership = await self._execute_scalar(membership_stmt)

        return None if membership is None else await self.get(default_id)

    async def create(
        self,
        workspace_id: UUID,
        name: str,
        owner_id: UUID,
        slug: str | None = None,
        is_default: bool = False,
        settings: WorkspaceSettings | None = None,
    ) -> Workspace:
        """Create a new workspace with owner membership.

        Args:
            workspace_id: UUID for the new workspace.
            name: Workspace name.
            owner_id: User UUID of the owner.
            slug: Optional URL slug.
            is_default: Whether this is the user's default workspace.
            settings: Optional workspace settings.

        Returns:
            Created workspace.
        """
        model = WorkspaceModel(
            id=workspace_id,
            name=name,
            slug=slug,
            is_default=is_default,
            settings=self._settings_to_dict(settings) if settings else {},
            metadata_={},
        )
        await self._add_and_flush(model)

        # Create owner membership
        membership = WorkspaceMembershipModel(
            workspace_id=workspace_id,
            user_id=owner_id,
            role=WorkspaceRole.OWNER.value,
        )
        await self._add_and_flush(membership)

        return self._to_domain(model)

    async def update(self, workspace: Workspace) -> Workspace:
        """Update an existing workspace.

        Args:
            workspace: Workspace with updated fields.

        Returns:
            Updated workspace.

        Raises:
            ValueError: If workspace does not exist.
        """
        stmt = select(WorkspaceModel).where(WorkspaceModel.id == workspace.id)
        model = await self._execute_scalar(stmt)

        if model is None:
            msg = f"Workspace {workspace.id} not found"
            raise ValueError(msg)

        model.name = workspace.name
        model.slug = workspace.slug
        model.settings = self._settings_to_dict(workspace.settings)
        model.metadata_ = workspace.metadata

        await self._session.flush()
        return self._to_domain(model)

    async def delete(self, workspace_id: UUID) -> bool:
        """Delete a workspace.

        Args:
            workspace_id: Workspace UUID.

        Returns:
            True if deleted, False if not found.
        """
        return await self._mixin_delete_by_id(workspace_id)

    async def list_for_user(
        self,
        user_id: UUID,
        limit: int = 50,
        offset: int = 0,
    ) -> Sequence[Workspace]:
        """List workspaces a user is a member of.

        Args:
            user_id: User UUID.
            limit: Maximum workspaces to return.
            offset: Pagination offset.

        Returns:
            List of workspaces.
        """
        stmt = (
            select(WorkspaceModel)
            .join(
                WorkspaceMembershipModel,
                WorkspaceModel.id == WorkspaceMembershipModel.workspace_id,
            )
            .where(WorkspaceMembershipModel.user_id == user_id)
            .order_by(WorkspaceModel.created_at.desc())
            .limit(limit)
            .offset(offset)
        )
        models = await self._execute_scalars(stmt)
        return [self._to_domain(m) for m in models]

    async def get_membership(
        self,
        workspace_id: UUID,
        user_id: UUID,
    ) -> WorkspaceMembership | None:
        """Get a user's membership in a workspace.

        Args:
            workspace_id: Workspace UUID.
            user_id: User UUID.

        Returns:
            Membership if user is a member, None otherwise.
        """
        stmt = select(WorkspaceMembershipModel).where(
            and_(
                WorkspaceMembershipModel.workspace_id == workspace_id,
                WorkspaceMembershipModel.user_id == user_id,
            ),
        )
        model = await self._execute_scalar(stmt)
        return self._membership_to_domain(model) if model else None

    async def add_member(
        self,
        workspace_id: UUID,
        user_id: UUID,
        role: WorkspaceRole,
    ) -> WorkspaceMembership:
        """Add a user to a workspace.

        Args:
            workspace_id: Workspace UUID.
            user_id: User UUID.
            role: Role to assign.

        Returns:
            Created membership.
        """
        model = WorkspaceMembershipModel(
            workspace_id=workspace_id,
            user_id=user_id,
            role=role.value,
        )
        await self._add_and_flush(model)
        return self._membership_to_domain(model)

    async def update_member_role(
        self,
        workspace_id: UUID,
        user_id: UUID,
        role: WorkspaceRole,
    ) -> WorkspaceMembership | None:
        """Update a member's role in a workspace.

        Args:
            workspace_id: Workspace UUID.
            user_id: User UUID.
            role: New role.

        Returns:
            Updated membership if found, None otherwise.
        """
        stmt = select(WorkspaceMembershipModel).where(
            and_(
                WorkspaceMembershipModel.workspace_id == workspace_id,
                WorkspaceMembershipModel.user_id == user_id,
            ),
        )
        model = await self._execute_scalar(stmt)

        if model is None:
            return None

        model.role = role.value
        await self._session.flush()
        return self._membership_to_domain(model)

    async def remove_member(
        self,
        workspace_id: UUID,
        user_id: UUID,
    ) -> bool:
        """Remove a user from a workspace.

        Args:
            workspace_id: Workspace UUID.
            user_id: User UUID.

        Returns:
            True if removed, False if not found.
        """
        stmt = select(WorkspaceMembershipModel).where(
            and_(
                WorkspaceMembershipModel.workspace_id == workspace_id,
                WorkspaceMembershipModel.user_id == user_id,
            ),
        )
        model = await self._execute_scalar(stmt)

        if model is None:
            return False

        await self._delete_and_flush(model)
        return True

    async def list_members(
        self,
        workspace_id: UUID,
        limit: int = 100,
        offset: int = 0,
    ) -> Sequence[WorkspaceMembership]:
        """List all members of a workspace.

        Args:
            workspace_id: Workspace UUID.
            limit: Maximum members to return.
            offset: Pagination offset.

        Returns:
            List of memberships.
        """
        stmt = (
            select(WorkspaceMembershipModel)
            .where(WorkspaceMembershipModel.workspace_id == workspace_id)
            .order_by(WorkspaceMembershipModel.created_at)
            .limit(limit)
            .offset(offset)
        )
        models = await self._execute_scalars(stmt)
        return [self._membership_to_domain(m) for m in models]
