from typing import Optional
from uuid import UUID

from arthur_common.models.enums import PaginationSortMethod, RuleScope, RuleType
from fastapi import HTTPException
from sqlalchemy import asc, desc, exists
from sqlalchemy.orm import Session

from db_models import DatabaseRule
from db_models.task_models import DatabaseTask, DatabaseTaskToRules
from schemas.internal_schemas import Rule
from utils import constants


class RuleRepository:
    def __init__(self, db_session: Session):
        self.db_session = db_session

    def get_rule_by_id(self, rule_id: str) -> Rule:
        rule = self.db_session.get(DatabaseRule, rule_id)
        if not rule:
            raise HTTPException(
                status_code=404,
                detail=constants.ERROR_RULE_NOT_FOUND % rule_id,
            )
        return Rule._from_database_model(rule)

    def query_rules(
        self,
        rule_ids: Optional[list[str]] = None,
        prompt_enabled: Optional[bool] = None,
        response_enabled: Optional[bool] = None,
        include_archived: bool = False,
        rule_scopes: Optional[list[RuleScope]] = None,
        rule_types: Optional[list[RuleType]] = None,
        sort: PaginationSortMethod = PaginationSortMethod.DESCENDING,
        page_size: Optional[int] = None,
        page: int = 0,
        org_scope: Optional[UUID] = None,
    ) -> tuple[list[Rule], int]:
        query = self.db_session.query(DatabaseRule)
        # Tenant callers see: default-scope rules (visible to everyone by design)
        # plus any task-scope rule linked to a task in their org. Admin
        # (org_scope=None) sees everything.
        if org_scope is not None:
            org_task_link = (
                self.db_session.query(DatabaseTaskToRules)
                .join(DatabaseTask, DatabaseTask.id == DatabaseTaskToRules.task_id)
                .where(DatabaseTaskToRules.rule_id == DatabaseRule.id)
                .where(DatabaseTask.org_id == org_scope)
            )
            query = query.where(
                (DatabaseRule.scope == RuleScope.DEFAULT)
                | exists(org_task_link.subquery().select()),
            )
        if rule_ids is not None:
            query = query.where(DatabaseRule.id.in_(rule_ids))
        if rule_scopes is not None:
            query = query.where(DatabaseRule.scope.in_(rule_scopes))
        if rule_types is not None:
            query = query.where(DatabaseRule.type.in_(rule_types))
        if prompt_enabled is not None:
            query = query.where(DatabaseRule.prompt_enabled.is_(prompt_enabled))
        if response_enabled is not None:
            query = query.where(DatabaseRule.response_enabled.is_(response_enabled))

        if sort == PaginationSortMethod.DESCENDING:
            query = query.order_by(desc(DatabaseRule.created_at))
        elif sort == PaginationSortMethod.ASCENDING:
            query = query.order_by(asc(DatabaseRule.created_at))

        query = query.where(DatabaseRule.archived == include_archived)
        count = query.count()

        if page and page_size:
            query = query.offset(page * page_size)
        if page_size:
            query = query.limit(page_size)

        results = query.all()

        rules = [Rule._from_database_model(op) for op in results]
        return rules, count

    def create_rule(self, rule: Rule) -> Rule:
        created_rule_db = rule._to_database_model()
        self.db_session.add(created_rule_db)
        self.db_session.commit()

        return Rule._from_database_model(created_rule_db)

    def archive_rule(self, rule_id: str, commit: bool = True) -> None:
        rule = self.db_session.get(DatabaseRule, rule_id)
        if not rule:
            raise HTTPException(
                status_code=404,
                detail=constants.ERROR_RULE_NOT_FOUND % rule_id,
            )
        rule.archived = True
        if commit:
            self.db_session.commit()

    def unarchive_rule(self, rule_id: str, commit: bool = True) -> None:
        rule = self.db_session.get(DatabaseRule, rule_id)
        if not rule:
            raise HTTPException(
                status_code=404,
                detail=constants.ERROR_RULE_NOT_FOUND % rule_id,
            )
        rule.archived = False
        if commit:
            self.db_session.commit()

    def delete_rule(self, rule_id: str) -> None:
        self.db_session.query(DatabaseRule).filter(DatabaseRule.id == rule_id).delete()
        self.db_session.commit()
