from sqlalchemy.ext.asyncio import AsyncSession

from src.db.models.project import Project
from src.db.models.app_user import AppUser
from src.db.models.role import UserRoleEnum
from src.core.errors import AccessDeniedException, ValidationException
from src.repository.project_repository import ProjectRepository
from src.repository.user_repository import UserRepository


class ProjectService:
    def __init__(self, db: AsyncSession):
        self.db = db
        self.project_repo = ProjectRepository(db)
        self.user_repo = UserRepository(db)

    async def get_list(
        self,
        user: AppUser,
        year: int | None = None,
        branch_id: int | None = None,
        offset: int | None = None,
        limit: int | None = None,
        with_count: bool = False,
        with_org_unit: bool = False,
    ) -> list[dict] | tuple[int, list[dict]]:
        org_unit_ids = await self._allowed_org_unit_ids(user)
        return await self.project_repo.get_list(
            year=year,
            branch_id=branch_id,
            offset=offset,
            limit=limit,
            with_count=with_count,
            org_unit_ids=org_unit_ids,
        )

    async def get(self, project_id: int, user: AppUser) -> dict | None:
        org_unit_ids = await self._allowed_org_unit_ids(user)
        project = await self.project_repo.get(project_id=project_id, org_unit_ids=org_unit_ids)
        if not project:
            return None
        project["reports"] = await self.project_repo.get_reports(project_id=project_id)
        return project

    async def get_instance(self, project_id: int, user: AppUser) -> Project | None:
        org_unit_ids = await self._allowed_org_unit_ids(user)
        return await self.project_repo.get_instance(project_id=project_id, org_unit_ids=org_unit_ids)

    async def resolve_report_id(
        self,
        project_id: int,
        year: int,
        report_type: str,
        user: AppUser,
    ) -> int | None:
        normalized_type = report_type.upper()
        if normalized_type not in ("LIMIT", "CURRENT_EXPENSES"):
            raise ValidationException("report_type должен быть LIMIT или CURRENT_EXPENSES")
        org_unit_ids = await self._allowed_org_unit_ids(user)
        return await self.project_repo.resolve_report_id(
            project_id=project_id,
            year=year,
            report_type=normalized_type,
            org_unit_ids=org_unit_ids,
        )

    async def get_report_rows(
        self,
        project_id: int,
        year: int,
        report_type: str,
        sections: list[str] | None,
        user: AppUser,
    ) -> list[tuple]:
        report_id = await self.resolve_report_id(project_id, year, report_type, user)
        if not report_id:
            raise ValidationException("Отчёт не найден")
        return await self.project_repo.get_report_rows(report_id=report_id, sections=sections)

    async def get_rf_rollup_rows(
        self,
        branch_id: int,
        year: int,
        sections: list[str] | None,
        user: AppUser,
    ) -> list[tuple]:
        org_unit_ids = await self._allowed_org_unit_ids(user)
        if org_unit_ids is not None and branch_id not in org_unit_ids:
            raise AccessDeniedException()
        return await self.project_repo.get_rf_rollup_rows(
            branch_id=branch_id,
            year=year,
            sections=sections,
        )

    async def upd_form3_cell(
        self,
        project_id: int,
        year: int,
        report_type: str,
        line_id: int,
        column: str,
        value,
        user: AppUser,
    ) -> list[tuple]:
        report_id = await self.resolve_report_id(project_id, year, report_type, user)
        if not report_id:
            raise ValidationException("Отчёт не найден")
        return await self.project_repo.upd_form3_cell(report_id, line_id, column, value)

    async def upd_form3_cells(
        self,
        project_id: int,
        year: int,
        report_type: str,
        changes: list[dict],
        user: AppUser,
    ) -> list[tuple]:
        report_id = await self.resolve_report_id(project_id, year, report_type, user)
        if not report_id:
            raise ValidationException("Отчёт не найден")
        return await self.project_repo.upd_form3_cells(report_id, changes)

    async def add_form3_line(
        self,
        project_id: int,
        year: int,
        report_type: str,
        expense_item_id: int,
        user: AppUser,
    ) -> list[tuple]:
        report_id = await self.resolve_report_id(project_id, year, report_type, user)
        if not report_id:
            raise ValidationException("Отчёт не найден")
        return await self.project_repo.add_form3_line(report_id, expense_item_id)

    async def del_form3_line(
        self,
        project_id: int,
        year: int,
        report_type: str,
        line_id: int,
        user: AppUser,
    ) -> list[tuple]:
        report_id = await self.resolve_report_id(project_id, year, report_type, user)
        if not report_id:
            raise ValidationException("Отчёт не найден")
        return await self.project_repo.del_form3_line(line_id)

    async def delete_project(self, project_id: int, user: AppUser):
        project = await self.get(project_id, user)
        if not project:
            raise ValidationException("Проект не найден")
        return await self.project_repo.del_project(project_id)

    async def upd_project(
        self,
        project_id: int,
        column: str,
        value,
        user: AppUser,
    ):
        project = await self.get(project_id, user)
        if not project:
            raise ValidationException("Проект не найден")
        return await self.project_repo.upd_project(project_id, column, value)

    async def add_project(
        self,
        name: str,
        year: int,
        org_unit_id: int,
        level: str = "project",
        parent_id: int | None = None,
        project_type: str | None = None,
        vsp_format: str | None = None,
        placement_type: str | None = None,
        object_address: str | None = None,
        staff_count: int | None = None,
        total_area: float | None = None,
    ) -> tuple[int, int, int]:
        return await self.project_repo.add_project(
            name=name,
            year=year,
            org_unit_id=org_unit_id,
            level=level,
            parent_id=parent_id,
            project_type=project_type,
            vsp_format=vsp_format,
            placement_type=placement_type,
            object_address=object_address,
            staff_count=staff_count,
            total_area=total_area,
        )

    async def _allowed_org_unit_ids(self, user: AppUser) -> list[int] | None:
        if user.role_id == UserRoleEnum.ADMIN:
            return None
        db_user = await self.user_repo.get(user_id=user.id, load_orgs=True)
        if not db_user:
            return []
        return [ou.id for ou in db_user.org_units]
