"""Channel operations for GreyMatter Solo.

Provides the business logic for channel operations (sync, share, retract,
review) with integrated audit logging. Every operation is automatically
recorded in the channel_audit_log table.

Usage:
    from greymatter_core.channel_ops import ChannelOperations

    ops = ChannelOperations(db)
    result = ops.sync_channel("ch-team", direction="push", target_node="node-2")
    result = ops.share_channel("ch-team", target_node="node-2", data_scope="knowledge")
    result = ops.retract_channel("ch-team", target_node="node-2")
    result = ops.review_channel("ch-team", verdict="approved")
"""
from __future__ import annotations

import logging
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Any

from .channel_audit import (
    AuditOutcome,
    AuditSeverity,
    ChannelAuditLogger,
    ChannelOperation,
)
from .db import Database

logger = logging.getLogger(__name__)


@dataclass
class OperationResult:
    """Result of a channel operation."""
    success: bool
    operation: str
    channel_id: str
    items_affected: int = 0
    detail: str = ""
    audit_event_id: str | None = None
    error: str | None = None

    def to_dict(self) -> dict[str, Any]:
        """Convert to dict for API responses."""
        d: dict[str, Any] = {
            "success": self.success,
            "operation": self.operation,
            "channel_id": self.channel_id,
            "items_affected": self.items_affected,
            "detail": self.detail,
        }
        if self.audit_event_id:
            d["audit_event_id"] = self.audit_event_id
        if self.error:
            d["error"] = self.error
        return d


class ChannelOperations:
    """Channel operations with integrated audit logging.

    Every operation logs to channel_audit_log before and after execution.
    High-severity events are also mirrored to security_events.
    """

    def __init__(
        self,
        db: Database,
        *,
        actor: str = "",
        actor_type: str = "system",
    ):
        self._db = db
        self._actor = actor
        self._actor_type = actor_type
        self._audit = ChannelAuditLogger(db)

    def _get_channel(self, channel_id: str) -> dict[str, Any] | None:
        """Look up a channel by ID."""
        return self._db.execute_one(
            "SELECT * FROM solo_channels WHERE id = ?",
            (channel_id,),
        )

    def sync_channel(
        self,
        channel_id: str,
        direction: str = "bidirectional",
        *,
        target_node: str | None = None,
        data_scope: str | None = None,
        payload: dict[str, Any] | None = None,
    ) -> OperationResult:
        """Sync a channel's data with a remote node.

        Args:
            channel_id: The channel to sync.
            direction: 'push', 'pull', or 'bidirectional'.
            target_node: Remote node to sync with.
            data_scope: Comma-separated list of tables to sync.
            payload: Additional sync parameters.

        Returns:
            OperationResult with success status and audit trail.
        """
        channel = self._get_channel(channel_id)
        if not channel:
            event_id = self._audit.log_sync(
                channel_id,
                direction,
                outcome=AuditOutcome.FAILED.value,
                detail=f"Channel not found: {channel_id}",
                actor=self._actor,
                actor_type=self._actor_type,
            )
            return OperationResult(
                success=False,
                operation="sync",
                channel_id=channel_id,
                detail=f"Channel not found: {channel_id}",
                audit_event_id=event_id,
                error="channel_not_found",
            )

        if not channel.get("sync_enabled"):
            event_id = self._audit.log_sync(
                channel_id,
                direction,
                outcome=AuditOutcome.DENIED.value,
                detail=f"Sync disabled for channel: {channel['name']}",
                actor=self._actor,
                actor_type=self._actor_type,
            )
            return OperationResult(
                success=False,
                operation="sync",
                channel_id=channel_id,
                detail=f"Sync disabled for channel: {channel['name']}",
                audit_event_id=event_id,
                error="sync_disabled",
            )

        # Perform the sync — this is where real sync logic would connect
        # to delta_sync / outbox. For now, we record the operation.
        items_affected = 0
        try:
            # Count items that would be synced based on data_scope
            tables = (data_scope or "observations,knowledge,patterns").split(",")
            for table in tables:
                table = table.strip()
                count_row = self._db.execute_one(
                    f"SELECT COUNT(*) AS cnt FROM {table}",
                )
                if count_row:
                    items_affected += count_row["cnt"]
        except Exception as e:
            logger.warning("Error counting sync items: %s", e)

        sync_payload = {
            "direction": direction,
            "channel_name": channel["name"],
            "classification": channel.get("classification", "unknown"),
            "encrypted": bool(channel.get("encrypted")),
            **(payload or {}),
        }

        event_id = self._audit.log_sync(
            channel_id,
            direction,
            items_affected=items_affected,
            outcome=AuditOutcome.SUCCESS.value,
            target_node=target_node,
            data_scope=data_scope or "observations,knowledge,patterns",
            detail=f"Synced {items_affected} items {direction}",
            payload=sync_payload,
            actor=self._actor,
            actor_type=self._actor_type,
        )

        return OperationResult(
            success=True,
            operation="sync",
            channel_id=channel_id,
            items_affected=items_affected,
            detail=f"Synced {items_affected} items {direction}",
            audit_event_id=event_id,
        )

    def share_channel(
        self,
        channel_id: str,
        target_node: str,
        *,
        data_scope: str | None = None,
        payload: dict[str, Any] | None = None,
    ) -> OperationResult:
        """Share a channel's data with another node.

        Args:
            channel_id: The channel to share.
            target_node: The node to share with.
            data_scope: What to share (e.g. 'knowledge,patterns').
            payload: Additional share parameters (e.g. item IDs).

        Returns:
            OperationResult with success status and audit trail.
        """
        channel = self._get_channel(channel_id)
        if not channel:
            event_id = self._audit.log_share(
                channel_id,
                target_node,
                outcome=AuditOutcome.FAILED.value,
                detail=f"Channel not found: {channel_id}",
                actor=self._actor,
                actor_type=self._actor_type,
            )
            return OperationResult(
                success=False,
                operation="share",
                channel_id=channel_id,
                detail=f"Channel not found: {channel_id}",
                audit_event_id=event_id,
                error="channel_not_found",
            )

        classification = channel.get("classification", "personal")
        if classification == "personal":
            event_id = self._audit.log_share(
                channel_id,
                target_node,
                outcome=AuditOutcome.DENIED.value,
                detail=f"Cannot share personal channel: {channel['name']}",
                actor=self._actor,
                actor_type=self._actor_type,
            )
            # Mirror to security_events — sharing personal data is a security event
            from .channel_audit import AuditEvent
            deny_event = AuditEvent(
                id="",
                channel_id=channel_id,
                operation="share",
                severity="high",
                outcome="denied",
                detail=f"Blocked: share of personal channel '{channel['name']}' to {target_node}",
            )
            self._audit.log_to_security_events(deny_event)

            return OperationResult(
                success=False,
                operation="share",
                channel_id=channel_id,
                detail=f"Cannot share personal channel: {channel['name']}",
                audit_event_id=event_id,
                error="personal_channel_share_denied",
            )

        # Count items that would be shared
        items_affected = 0
        tables = (data_scope or "observations,knowledge,patterns").split(",")
        for table in tables:
            table = table.strip()
            try:
                count_row = self._db.execute_one(
                    f"SELECT COUNT(*) AS cnt FROM {table}",
                )
                if count_row:
                    items_affected += count_row["cnt"]
            except Exception:
                pass

        share_payload = {
            "channel_name": channel["name"],
            "classification": classification,
            "encrypted": bool(channel.get("encrypted")),
            **(payload or {}),
        }

        event_id = self._audit.log_share(
            channel_id,
            target_node,
            items_affected=items_affected,
            outcome=AuditOutcome.SUCCESS.value,
            data_scope=data_scope or "observations,knowledge,patterns",
            detail=f"Shared {items_affected} items with {target_node}",
            payload=share_payload,
            actor=self._actor,
            actor_type=self._actor_type,
        )

        return OperationResult(
            success=True,
            operation="share",
            channel_id=channel_id,
            items_affected=items_affected,
            detail=f"Shared {items_affected} items with {target_node}",
            audit_event_id=event_id,
        )

    def retract_channel(
        self,
        channel_id: str,
        *,
        target_node: str | None = None,
        data_scope: str | None = None,
        reason: str = "",
        payload: dict[str, Any] | None = None,
    ) -> OperationResult:
        """Retract previously shared data from a channel.

        This is a destructive operation that withdraws data from remote nodes.
        Always logged at high severity and mirrored to security_events.

        Args:
            channel_id: The channel whose data to retract.
            target_node: Retract from a specific node (None = all nodes).
            data_scope: What to retract.
            reason: Why the data is being retracted.
            payload: Additional retract parameters.

        Returns:
            OperationResult with success status and audit trail.
        """
        channel = self._get_channel(channel_id)
        if not channel:
            event_id = self._audit.log_retract(
                channel_id,
                outcome=AuditOutcome.FAILED.value,
                target_node=target_node,
                detail=f"Channel not found: {channel_id}",
                actor=self._actor,
                actor_type=self._actor_type,
            )
            return OperationResult(
                success=False,
                operation="retract",
                channel_id=channel_id,
                detail=f"Channel not found: {channel_id}",
                audit_event_id=event_id,
                error="channel_not_found",
            )

        retract_payload = {
            "channel_name": channel["name"],
            "reason": reason,
            "target": target_node or "all_nodes",
            **(payload or {}),
        }

        # Count items affected
        items_affected = 0
        tables = (data_scope or "observations,knowledge,patterns").split(",")
        for table in tables:
            table = table.strip()
            try:
                count_row = self._db.execute_one(
                    f"SELECT COUNT(*) AS cnt FROM {table}",
                )
                if count_row:
                    items_affected += count_row["cnt"]
            except Exception:
                pass

        target_desc = target_node or "all nodes"
        detail = f"Retracted {items_affected} items from {target_desc}"
        if reason:
            detail += f" ({reason})"

        event_id = self._audit.log_retract(
            channel_id,
            items_affected=items_affected,
            outcome=AuditOutcome.SUCCESS.value,
            target_node=target_node,
            data_scope=data_scope or "observations,knowledge,patterns",
            detail=detail,
            payload=retract_payload,
            actor=self._actor,
            actor_type=self._actor_type,
        )

        # Always mirror retracts to security_events
        from .channel_audit import AuditEvent
        retract_event = AuditEvent(
            id="",
            channel_id=channel_id,
            operation="retract",
            severity="high",
            outcome="success",
            detail=detail,
        )
        self._audit.log_to_security_events(retract_event)

        return OperationResult(
            success=True,
            operation="retract",
            channel_id=channel_id,
            items_affected=items_affected,
            detail=detail,
            audit_event_id=event_id,
        )

    def review_channel(
        self,
        channel_id: str,
        *,
        verdict: str = "approved",
        notes: str = "",
        data_scope: str | None = None,
        items_reviewed: int = 0,
        payload: dict[str, Any] | None = None,
    ) -> OperationResult:
        """Review a channel's contents or recent operations.

        Args:
            channel_id: The channel to review.
            verdict: 'approved', 'rejected', or 'pending'.
            notes: Review notes or findings.
            data_scope: What was reviewed.
            items_reviewed: How many items were inspected.
            payload: Structured review findings.

        Returns:
            OperationResult with success status and audit trail.
        """
        channel = self._get_channel(channel_id)
        if not channel:
            event_id = self._audit.log_review(
                channel_id,
                outcome=AuditOutcome.FAILED.value,
                detail=f"Channel not found: {channel_id}",
                actor=self._actor,
                actor_type=self._actor_type,
            )
            return OperationResult(
                success=False,
                operation="review",
                channel_id=channel_id,
                detail=f"Channel not found: {channel_id}",
                audit_event_id=event_id,
                error="channel_not_found",
            )

        outcome_map = {
            "approved": AuditOutcome.SUCCESS.value,
            "rejected": AuditOutcome.FAILED.value,
            "pending": AuditOutcome.PENDING.value,
        }
        outcome = outcome_map.get(verdict, AuditOutcome.SUCCESS.value)

        review_payload = {
            "channel_name": channel["name"],
            "verdict": verdict,
            "notes": notes,
            **(payload or {}),
        }

        detail = f"Review {verdict}: {channel['name']}"
        if notes:
            detail += f" — {notes}"

        event_id = self._audit.log_review(
            channel_id,
            outcome=outcome,
            detail=detail,
            payload=review_payload,
            actor=self._actor,
            actor_type=self._actor_type,
            items_affected=items_reviewed,
            data_scope=data_scope,
        )

        return OperationResult(
            success=verdict != "rejected",
            operation="review",
            channel_id=channel_id,
            items_affected=items_reviewed,
            detail=detail,
            audit_event_id=event_id,
        )
