"""Proto ↔ domain converters for project management."""

from __future__ import annotations

from collections.abc import MutableSequence, Sequence
from typing import TYPE_CHECKING, Protocol, cast
from uuid import UUID

from noteflow.domain.entities.project import ExportRules, ProjectSettings, TriggerRules
from noteflow.domain.identity import ProjectRole
from noteflow.domain.value_objects import ExportFormat

from ...proto import noteflow_pb2
from ..converters import export_format_to_proto

if TYPE_CHECKING:
    from noteflow.domain.entities.project import Project
    from noteflow.domain.identity import ProjectMembership


class _HasField(Protocol):
    def HasField(self, field_name: str) -> bool: ...


class _Copyable(Protocol):
    def CopyFrom(self, other: _Copyable) -> None: ...


def proto_to_export_format(proto_fmt: int) -> ExportFormat | None:
    """Convert proto enum to domain ExportFormat."""
    mapping: dict[int, ExportFormat] = {
        noteflow_pb2.EXPORT_FORMAT_MARKDOWN: ExportFormat.MARKDOWN,
        noteflow_pb2.EXPORT_FORMAT_HTML: ExportFormat.HTML,
        noteflow_pb2.EXPORT_FORMAT_PDF: ExportFormat.PDF,
    }
    return mapping.get(proto_fmt)


def project_role_to_proto(role: ProjectRole) -> int:
    """Convert domain ProjectRole to proto enum."""
    mapping: dict[ProjectRole, int] = {
        ProjectRole.VIEWER: noteflow_pb2.PROJECT_ROLE_VIEWER,
        ProjectRole.EDITOR: noteflow_pb2.PROJECT_ROLE_EDITOR,
        ProjectRole.ADMIN: noteflow_pb2.PROJECT_ROLE_ADMIN,
    }
    return mapping.get(role, noteflow_pb2.PROJECT_ROLE_UNSPECIFIED)


def proto_to_project_role(proto_role: int) -> ProjectRole:
    """Convert proto enum to domain ProjectRole."""
    mapping: dict[int, ProjectRole] = {
        noteflow_pb2.PROJECT_ROLE_VIEWER: ProjectRole.VIEWER,
        noteflow_pb2.PROJECT_ROLE_EDITOR: ProjectRole.EDITOR,
        noteflow_pb2.PROJECT_ROLE_ADMIN: ProjectRole.ADMIN,
    }
    return mapping.get(proto_role, ProjectRole.VIEWER)


def export_rules_to_proto(rules: ExportRules | None) -> noteflow_pb2.ExportRulesProto | None:
    """Convert domain ExportRules to proto message."""
    if rules is None:
        return None

    proto = noteflow_pb2.ExportRulesProto()
    if rules.default_format is not None:
        proto.default_format = export_format_to_proto(rules.default_format)
    if rules.include_audio is not None:
        proto.include_audio = rules.include_audio
    if rules.include_timestamps is not None:
        proto.include_timestamps = rules.include_timestamps
    if rules.template_id is not None:
        proto.template_id = str(rules.template_id)
    return proto


def proto_to_export_rules(proto: noteflow_pb2.ExportRulesProto | None) -> ExportRules | None:
    """Convert proto message to domain ExportRules."""
    if proto is None:
        return None

    default_format = None
    if cast(_HasField, proto).HasField("default_format"):
        default_format = proto_to_export_format(proto.default_format)

    include_audio = (
        proto.include_audio
        if cast(_HasField, proto).HasField("include_audio")
        else None
    )
    include_timestamps = (
        proto.include_timestamps
        if cast(_HasField, proto).HasField("include_timestamps")
        else None
    )
    template_id = (
        UUID(proto.template_id)
        if cast(_HasField, proto).HasField("template_id")
        else None
    )

    return ExportRules(
        default_format=default_format,
        include_audio=include_audio,
        include_timestamps=include_timestamps,
        template_id=template_id,
    )


def trigger_rules_to_proto(rules: TriggerRules | None) -> noteflow_pb2.TriggerRulesProto | None:
    """Convert domain TriggerRules to proto message."""
    if rules is None:
        return None

    proto = noteflow_pb2.TriggerRulesProto()
    if rules.auto_start_enabled is not None:
        proto.auto_start_enabled = rules.auto_start_enabled
    if rules.calendar_match_patterns is not None:
        calendar_patterns = cast(Sequence[str], rules.calendar_match_patterns)
        calendar_field = cast(MutableSequence[str], proto.calendar_match_patterns)
        calendar_field.extend(calendar_patterns)
    if rules.app_match_patterns is not None:
        app_patterns = cast(Sequence[str], rules.app_match_patterns)
        app_field = cast(MutableSequence[str], proto.app_match_patterns)
        app_field.extend(app_patterns)
    return proto


def proto_to_trigger_rules(proto: noteflow_pb2.TriggerRulesProto | None) -> TriggerRules | None:
    """Convert proto message to domain TriggerRules."""
    if proto is None:
        return None

    auto_start = (
        proto.auto_start_enabled
        if cast(_HasField, proto).HasField("auto_start_enabled")
        else None
    )
    calendar_values = cast(Sequence[str], proto.calendar_match_patterns)
    calendar_patterns = list(calendar_values) if calendar_values else None
    app_values = cast(Sequence[str], proto.app_match_patterns)
    app_patterns = list(app_values) if app_values else None

    return TriggerRules(
        auto_start_enabled=auto_start,
        calendar_match_patterns=calendar_patterns,
        app_match_patterns=app_patterns,
    )


def project_settings_to_proto(
    settings: ProjectSettings | None,
) -> noteflow_pb2.ProjectSettingsProto | None:
    """Convert domain ProjectSettings to proto message."""
    if settings is None:
        return None

    proto = noteflow_pb2.ProjectSettingsProto()
    if settings.export_rules is not None:
        export_rules_proto = export_rules_to_proto(settings.export_rules)
        if export_rules_proto is not None:
            export_rules_field = cast(_Copyable, proto.export_rules)
            export_rules_field.CopyFrom(cast(_Copyable, export_rules_proto))
    if settings.trigger_rules is not None:
        trigger_rules_proto = trigger_rules_to_proto(settings.trigger_rules)
        if trigger_rules_proto is not None:
            trigger_rules_field = cast(_Copyable, proto.trigger_rules)
            trigger_rules_field.CopyFrom(cast(_Copyable, trigger_rules_proto))
    if settings.rag_enabled is not None:
        proto.rag_enabled = settings.rag_enabled
    if settings.default_summarization_template is not None:
        proto.default_summarization_template = settings.default_summarization_template
    return proto


def proto_to_project_settings(
    proto: noteflow_pb2.ProjectSettingsProto | None,
) -> ProjectSettings | None:
    """Convert proto message to domain ProjectSettings."""
    if proto is None:
        return None

    export_rules = (
        proto_to_export_rules(proto.export_rules)
        if cast(_HasField, proto).HasField("export_rules")
        else None
    )
    trigger_rules = (
        proto_to_trigger_rules(proto.trigger_rules)
        if cast(_HasField, proto).HasField("trigger_rules")
        else None
    )
    rag_enabled = (
        proto.rag_enabled
        if cast(_HasField, proto).HasField("rag_enabled")
        else None
    )
    default_template = (
        proto.default_summarization_template
        if cast(_HasField, proto).HasField("default_summarization_template")
        else None
    )

    return ProjectSettings(
        export_rules=export_rules,
        trigger_rules=trigger_rules,
        rag_enabled=rag_enabled,
        default_summarization_template=default_template,
    )


def project_to_proto(project: Project) -> noteflow_pb2.ProjectProto:
    """Convert domain Project to proto message."""
    proto = noteflow_pb2.ProjectProto(
        id=str(project.id),
        workspace_id=str(project.workspace_id),
        name=project.name,
        is_default=project.is_default,
        is_archived=project.is_archived,
        created_at=int(project.created_at.timestamp()),
        updated_at=int(project.updated_at.timestamp()),
    )

    if project.slug is not None:
        proto.slug = project.slug
    if project.description is not None:
        proto.description = project.description
    settings_proto = project_settings_to_proto(project.settings)
    if settings_proto is not None:
        settings_field = cast(_Copyable, proto.settings)
        settings_field.CopyFrom(cast(_Copyable, settings_proto))
    if project.archived_at is not None:
        proto.archived_at = int(project.archived_at.timestamp())

    return proto


def membership_to_proto(membership: ProjectMembership) -> noteflow_pb2.ProjectMembershipProto:
    """Convert domain ProjectMembership to proto message."""
    return noteflow_pb2.ProjectMembershipProto(
        project_id=str(membership.project_id),
        user_id=str(membership.user_id),
        role=project_role_to_proto(membership.role),
        joined_at=int(membership.joined_at.timestamp()),
    )
