import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple

from sqlalchemy.orm import Session as OrmSession

from app.core.config import settings
from app.core.errors import AppError
from app.i18n.loader import t
from app.logging.activity_logger import ActivityLogger
from app.logging.models import ActivityLog
from app.models.catalog import ProjectRegistry, ProjectMember, UserAccount


def _now() -> str:
    return datetime.now(timezone.utc).isoformat()


def _new_id() -> str:
    return str(uuid.uuid4())


class ProjectService:
    def __init__(self, db: OrmSession) -> None:
        self._db = db
        self._logger = ActivityLogger(db)

    # -- helpers -----------------------------------------------------------

    def _check_access(self, project: ProjectRegistry, user: UserAccount) -> Optional[ProjectMember]:
        """Return the user's membership or None. Admins always have access."""
        if user.role == "admin":
            return (
                self._db.query(ProjectMember)
                .filter(ProjectMember.project_id == project.id, ProjectMember.user_id == user.id)
                .first()
            )
        member = (
            self._db.query(ProjectMember)
            .filter(ProjectMember.project_id == project.id, ProjectMember.user_id == user.id)
            .first()
        )
        if not member:
            raise AppError(
                code="project.access_denied",
                message=t("project.access_denied"),
                status_code=403,
            )
        return member

    def _check_modify(self, project: ProjectRegistry, user: UserAccount) -> None:
        if user.role == "admin":
            return
        member = (
            self._db.query(ProjectMember)
            .filter(ProjectMember.project_id == project.id, ProjectMember.user_id == user.id)
            .first()
        )
        if not member or member.role != "owner":
            raise AppError(
                code="project.modify_denied",
                message=t("project.modify_denied"),
                status_code=403,
            )

    def _get_project(self, project_id: str) -> ProjectRegistry:
        project = (
            self._db.query(ProjectRegistry)
            .filter(ProjectRegistry.id == project_id, ProjectRegistry.status != "deleted")
            .first()
        )
        if not project:
            raise AppError(
                code="project.not_found",
                message=t("project.not_found"),
                status_code=404,
            )
        return project

    # -- projects ----------------------------------------------------------

    def list_projects(
        self, user: UserAccount, page: int = 1, per_page: int = 20
    ) -> Tuple[List[ProjectRegistry], int]:
        q = self._db.query(ProjectRegistry).filter(ProjectRegistry.status != "deleted")
        if user.role != "admin":
            member_project_ids = (
                self._db.query(ProjectMember.project_id)
                .filter(ProjectMember.user_id == user.id)
                .scalar_subquery()
            )
            q = q.filter(ProjectRegistry.id.in_(member_project_ids))
        total = q.count()
        items = q.order_by(ProjectRegistry.created_at.desc()).offset((page - 1) * per_page).limit(per_page).all()
        return items, total

    def create_project(
        self,
        name: str,
        description: str,
        creator_user: UserAccount,
        ip: Optional[str] = None,
    ) -> ProjectRegistry:
        project_id = _new_id()

        # Create directory structure for assets (images, exports, etc.)
        base = Path(settings.projects_dir)
        project_dir = base / project_id
        for sub in ["assets/screenplays", "assets/references", "assets/generated", "assets/exports"]:
            (project_dir / sub).mkdir(parents=True, exist_ok=True)

        now = _now()
        project = ProjectRegistry(
            id=project_id,
            name=name,
            description=description or "",
            status="active",
            created_by=creator_user.id,
            created_at=now,
            updated_at=now,
        )
        self._db.add(project)
        self._db.flush()

        member = ProjectMember(
            id=_new_id(),
            project_id=project_id,
            user_id=creator_user.id,
            role="owner",
            added_by=None,
            created_at=now,
        )
        self._db.add(member)
        self._db.commit()

        self._logger.log(
            actor_id=creator_user.id,
            action="project.create",
            resource_type="project",
            resource_id=project_id,
            project_id=project_id,
            detail={"name": name},
            ip_address=ip,
        )

        return project

    def _resolve_username(self, user_id: str) -> str:
        u = self._db.query(UserAccount).filter(UserAccount.id == user_id).first()
        return u.display_name if u else user_id

    def get_project(self, project_id: str, user: UserAccount) -> Dict[str, Any]:
        project = self._get_project(project_id)
        membership = self._check_access(project, user)
        member_count = (
            self._db.query(ProjectMember)
            .filter(ProjectMember.project_id == project_id)
            .count()
        )
        from app.schemas.project import ProjectResponse
        proj_dict = ProjectResponse.model_validate(project).model_dump()
        proj_dict["member_count"] = member_count
        proj_dict["my_role"] = membership.role if membership else ("admin" if user.role == "admin" else None)
        proj_dict["created_by"] = self._resolve_username(project.created_by)
        return proj_dict

    def update_project(
        self,
        project_id: str,
        updates: Dict[str, Any],
        user: UserAccount,
        ip: Optional[str] = None,
    ) -> ProjectRegistry:
        project = self._get_project(project_id)
        self._check_modify(project, user)

        before: Dict[str, Any] = {}
        after: Dict[str, Any] = {}
        for field in ("name", "description", "status"):
            if field in updates and updates[field] is not None:
                before[field] = getattr(project, field)
                setattr(project, field, updates[field])
                after[field] = updates[field]

        project.updated_at = _now()
        self._db.commit()

        self._logger.log(
            actor_id=user.id,
            action="project.update",
            resource_type="project",
            resource_id=project_id,
            project_id=project_id,
            detail={"before": before, "after": after},
            ip_address=ip,
        )
        return project

    def delete_project(
        self,
        project_id: str,
        user: UserAccount,
        ip: Optional[str] = None,
    ) -> ProjectRegistry:
        project = self._get_project(project_id)
        self._check_modify(project, user)

        project.status = "deleted"
        project.updated_at = _now()
        self._db.commit()

        self._logger.log(
            actor_id=user.id,
            action="project.delete",
            resource_type="project",
            resource_id=project_id,
            project_id=project_id,
            ip_address=ip,
        )
        return project

    # -- members -----------------------------------------------------------

    def list_members(
        self, project_id: str, user: UserAccount
    ) -> List[ProjectMember]:
        project = self._get_project(project_id)
        self._check_access(project, user)
        return (
            self._db.query(ProjectMember)
            .filter(ProjectMember.project_id == project_id)
            .all()
        )

    def add_member(
        self,
        project_id: str,
        user_id: str,
        role: str,
        actor_user: UserAccount,
        ip: Optional[str] = None,
    ) -> ProjectMember:
        project = self._get_project(project_id)
        self._check_modify(project, actor_user)

        existing = (
            self._db.query(ProjectMember)
            .filter(ProjectMember.project_id == project_id, ProjectMember.user_id == user_id)
            .first()
        )
        if existing:
            raise AppError(
                code="member.already_exists",
                message=t("member.already_exists"),
                status_code=400,
            )

        member = ProjectMember(
            id=_new_id(),
            project_id=project_id,
            user_id=user_id,
            role=role,
            added_by=actor_user.id,
            created_at=_now(),
        )
        self._db.add(member)
        self._db.commit()

        self._logger.log(
            actor_id=actor_user.id,
            action="member.add",
            resource_type="member",
            resource_id=member.id,
            project_id=project_id,
            detail={"user_id": user_id, "role": role},
            ip_address=ip,
        )
        return member

    def update_member_role(
        self,
        project_id: str,
        target_user_id: str,
        new_role: str,
        actor_user: UserAccount,
        ip: Optional[str] = None,
    ) -> ProjectMember:
        project = self._get_project(project_id)
        self._check_modify(project, actor_user)

        member = (
            self._db.query(ProjectMember)
            .filter(ProjectMember.project_id == project_id, ProjectMember.user_id == target_user_id)
            .first()
        )
        if not member:
            raise AppError(
                code="member.not_found",
                message=t("member.not_found"),
                status_code=404,
            )

        # If demoting from owner, ensure at least 1 owner remains
        if member.role == "owner" and new_role != "owner":
            owner_count = (
                self._db.query(ProjectMember)
                .filter(ProjectMember.project_id == project_id, ProjectMember.role == "owner")
                .count()
            )
            if owner_count <= 1:
                raise AppError(
                    code="member.must_have_owner",
                    message=t("member.must_have_owner"),
                    status_code=400,
                )

        old_role = member.role
        member.role = new_role
        self._db.commit()

        self._logger.log(
            actor_id=actor_user.id,
            action="member.role_change",
            resource_type="member",
            resource_id=member.id,
            project_id=project_id,
            detail={"user_id": target_user_id, "before": old_role, "after": new_role},
            ip_address=ip,
        )
        return member

    def remove_member(
        self,
        project_id: str,
        target_user_id: str,
        actor_user: UserAccount,
        ip: Optional[str] = None,
    ) -> None:
        project = self._get_project(project_id)
        self._check_modify(project, actor_user)

        member = (
            self._db.query(ProjectMember)
            .filter(ProjectMember.project_id == project_id, ProjectMember.user_id == target_user_id)
            .first()
        )
        if not member:
            raise AppError(
                code="member.not_found",
                message=t("member.not_found"),
                status_code=404,
            )

        if member.role == "owner":
            raise AppError(
                code="member.cannot_remove_owner",
                message=t("member.cannot_remove_owner"),
                status_code=400,
            )

        member_id = member.id
        self._db.delete(member)
        self._db.commit()

        self._logger.log(
            actor_id=actor_user.id,
            action="member.remove",
            resource_type="member",
            resource_id=member_id,
            project_id=project_id,
            detail={"user_id": target_user_id},
            ip_address=ip,
        )

    # -- activities --------------------------------------------------------

    def list_activities(
        self,
        page: int = 1,
        per_page: int = 20,
        project_id: Optional[str] = None,
        action: Optional[str] = None,
        actor_id: Optional[str] = None,
        date_from: Optional[str] = None,
        date_to: Optional[str] = None,
    ) -> Tuple[List[ActivityLog], int]:
        q = self._db.query(ActivityLog)
        if project_id:
            q = q.filter(ActivityLog.project_id == project_id)
        if action:
            q = q.filter(ActivityLog.action == action)
        if actor_id:
            q = q.filter(ActivityLog.actor_id == actor_id)
        if date_from:
            q = q.filter(ActivityLog.created_at >= date_from)
        if date_to:
            q = q.filter(ActivityLog.created_at <= date_to)
        total = q.count()
        items = q.order_by(ActivityLog.created_at.desc()).offset((page - 1) * per_page).limit(per_page).all()
        return items, total
