"""
Access Control Service.

Handles access control for dynamic executions - requesting, granting, and denying access.
"""

from typing import Dict, Any, Optional
from datetime import datetime, timezone
import structlog

from sqlalchemy.orm import Session
from sqlalchemy.orm.attributes import flag_modified

from control_plane_api.app.models.execution import Execution
from control_plane_api.app.models.associations import ExecutionParticipant
from control_plane_api.app.services.knock_service import knock_service

logger = structlog.get_logger()


class AccessControlService:
    """Service for managing execution access control"""

    async def request_access(
        self,
        db: Session,
        execution_id: str,
        organization_id: str,
        user_id: str,
        user_name: str,
        user_email: str,
    ) -> Dict[str, Any]:
        """
        Request access to a private execution.

        Args:
            db: Database session
            execution_id: Execution ID
            organization_id: Organization ID
            user_id: Requesting user ID
            user_name: Requesting user name
            user_email: Requesting user email

        Returns:
            {"status": "request_sent" | "already_has_access" | "request_pending"}
        """
        # Fetch execution
        execution = db.query(Execution).filter(
            Execution.id == execution_id,
            Execution.organization_id == organization_id,
        ).first()

        if not execution:
            raise ValueError("Execution not found")

        # Check if already has access
        participant = db.query(ExecutionParticipant).filter(
            ExecutionParticipant.execution_id == execution_id,
            ExecutionParticipant.user_id == user_id,
        ).first()

        if participant:
            return {"status": "already_has_access"}

        # Check if already requested
        metadata = execution.execution_metadata or {}
        share_requests = metadata.get("share_requests", [])

        if any(r.get("user_id") == user_id for r in share_requests):
            return {"status": "request_pending"}

        # Add to share_requests
        share_requests.append({
            "user_id": user_id,
            "user_name": user_name,
            "user_email": user_email,
            "requested_at": datetime.now(timezone.utc).isoformat(),
        })

        metadata["share_requests"] = share_requests
        execution.execution_metadata = metadata
        flag_modified(execution, "execution_metadata")
        db.commit()

        logger.info(
            "Access request created",
            execution_id=execution_id,
            requester_id=user_id,
        )

        # Get owner and send notification
        owner = db.query(ExecutionParticipant).filter(
            ExecutionParticipant.execution_id == execution_id,
            ExecutionParticipant.role == "owner",
        ).first()

        if owner:
            await knock_service.send_access_request_notification(
                execution_id=execution_id,
                execution_name=execution.entity_name,
                requester_id=user_id,
                requester_name=user_name,
                requester_email=user_email,
                owner_id=owner.user_id,
                owner_email=owner.user_email,
            )

        return {"status": "request_sent"}

    async def grant_access(
        self,
        db: Session,
        execution_id: str,
        organization_id: str,
        owner_user_id: str,
        target_user_id: str,
    ) -> Dict[str, Any]:
        """
        Grant access to a user (owner only).

        Args:
            db: Database session
            execution_id: Execution ID
            organization_id: Organization ID
            owner_user_id: Owner user ID (for verification)
            target_user_id: User to grant access to

        Returns:
            {"status": "access_granted"}
        """
        # Verify requester is owner
        owner = db.query(ExecutionParticipant).filter(
            ExecutionParticipant.execution_id == execution_id,
            ExecutionParticipant.user_id == owner_user_id,
            ExecutionParticipant.role == "owner",
        ).first()

        if not owner:
            raise PermissionError("Only owner can grant access")

        # Fetch execution and user info from share request
        execution = db.query(Execution).filter(Execution.id == execution_id).first()
        metadata = execution.execution_metadata or {}
        share_requests = metadata.get("share_requests", [])

        user_request = next((r for r in share_requests if r["user_id"] == target_user_id), None)
        if not user_request:
            raise ValueError("Access request not found")

        # Add participant
        participant = ExecutionParticipant(
            execution_id=execution_id,
            organization_id=organization_id,
            user_id=target_user_id,
            user_name=user_request.get("user_name"),
            user_email=user_request.get("user_email"),
            role="viewer",
        )
        db.add(participant)

        # Remove from share_requests
        metadata["share_requests"] = [r for r in share_requests if r["user_id"] != target_user_id]
        execution.execution_metadata = metadata
        flag_modified(execution, "execution_metadata")
        db.commit()

        logger.info(
            "Access granted",
            execution_id=execution_id,
            user_id=target_user_id,
        )

        # Send notification
        await knock_service.send_access_granted_notification(
            execution_id=execution_id,
            execution_name=execution.entity_name,
            granted_user_id=target_user_id,
            granted_user_email=user_request.get("user_email"),
        )

        return {"status": "access_granted"}

    async def deny_access(
        self,
        db: Session,
        execution_id: str,
        owner_user_id: str,
        target_user_id: str,
        reason: Optional[str] = None,
    ) -> Dict[str, Any]:
        """
        Deny access request (owner only).

        Args:
            db: Database session
            execution_id: Execution ID
            owner_user_id: Owner user ID (for verification)
            target_user_id: User to deny
            reason: Optional reason for denial

        Returns:
            {"status": "access_denied"}
        """
        # Verify requester is owner
        owner = db.query(ExecutionParticipant).filter(
            ExecutionParticipant.execution_id == execution_id,
            ExecutionParticipant.user_id == owner_user_id,
            ExecutionParticipant.role == "owner",
        ).first()

        if not owner:
            raise PermissionError("Only owner can deny access")

        # Remove from share_requests
        execution = db.query(Execution).filter(Execution.id == execution_id).first()
        metadata = execution.execution_metadata or {}
        share_requests = metadata.get("share_requests", [])

        user_request = next((r for r in share_requests if r["user_id"] == target_user_id), None)
        if not user_request:
            raise ValueError("Access request not found")

        metadata["share_requests"] = [r for r in share_requests if r["user_id"] != target_user_id]
        execution.execution_metadata = metadata
        flag_modified(execution, "execution_metadata")
        db.commit()

        logger.info(
            "Access denied",
            execution_id=execution_id,
            user_id=target_user_id,
        )

        # Send notification
        await knock_service.send_access_denied_notification(
            execution_id=execution_id,
            denied_user_id=target_user_id,
            denied_user_email=user_request.get("user_email"),
            reason=reason,
        )

        return {"status": "access_denied"}


# Singleton instance
access_control_service = AccessControlService()
