from abc import ABC
from typing import Iterable, Optional, Type

from django.db.backends.base.schema import BaseDatabaseSchemaEditor as SchemaEditor
from django.db.migrations.operations.base import Operation
from django.db.migrations.state import ProjectState
from django.db.models import Model


class ModelAccessOperation(ABC, Operation):
    def __init__(
        self,
        models: Iterable[Type[Model]],
        roles: Iterable[str],
        accesses: Iterable[str] = ('SELECT', ),
    ):  # noqa: D205, D400, D415
        """
        Args:
            models: An iterable of models to which the operation applies.
            roles: An iterable of roles to which the accesses apply.
            accesses: An iterable of accesses to manage.

        """
        self.accesses = ', '.join(access.upper() for access in accesses)
        self.tables = ', '.join(f'"{model._meta.db_table}"' for model in models)
        self.roles = ', '.join(f'"{role}"' for role in roles)

    def state_forwards(self, app_label: str, state: ProjectState) -> None:
        pass


class GrantModelAccess(ModelAccessOperation):
    """
    Grant access to a given model.
    """
    def database_forwards(
        self, app_label: str, schema_editor: SchemaEditor,
        from_state: Optional[ProjectState] = None, to_state: Optional[ProjectState] = None,
    ) -> None:
        schema_editor.execute(f'GRANT {self.accesses} ON {self.tables} TO {self.roles}')

    def database_backwards(
        self, app_label: str, schema_editor: SchemaEditor,
        from_state: Optional[ProjectState] = None, to_state: Optional[ProjectState] = None,
    ) -> None:
        schema_editor.execute(f'REVOKE {self.accesses} ON {self.tables} FROM {self.roles}')

    def describe(self) -> str:
        return f'Grant access on {self.tables} to {self.roles}'


class RevokeModelAccess(ModelAccessOperation):
    """
    Revoke access from a given model.
    """
    def database_forwards(
        self, app_label: str, schema_editor: SchemaEditor,
        from_state: Optional[ProjectState] = None, to_state: Optional[ProjectState] = None,
    ) -> None:
        schema_editor.execute(f'REVOKE {self.accesses} ON {self.tables} FROM {self.roles}')

    def database_backwards(
        self, app_label: str, schema_editor: SchemaEditor,
        from_state: Optional[ProjectState] = None, to_state: Optional[ProjectState] = None,
    ) -> None:
        schema_editor.execute(f'GRANT {self.accesses} ON {self.tables} TO {self.roles}')

    def describe(self) -> str:
        return f'Revoke access on {self.tables} from {self.roles}'
