import logging
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

logger = logging.getLogger(__name__)
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())


def generate_english_name(korean_name: str) -> str:
    """한국어 프로젝트명을 영어로 변환. gpt-nano 사용. 실패 시 romanization fallback."""
    try:
        from app.modules.llm.llm_client import call_text
        result = call_text(
            step="project_summary",
            system_prompt="You are a translator. The input is a Korean film/drama project name. Translate or transliterate it to English. For proper nouns (titles), use the official romanization or a natural English equivalent. Reply with ONLY the English name, nothing else. Use title case. Keep version numbers as-is.",
            user_prompt=korean_name,
            project_config={"project_summary": {"model": "gpt"}},
            temperature=0.0,
        )
        name = result.strip().strip('"').strip("'")
        if name and len(name) < 100:
            return name
    except Exception as exc:
        logger.warning("English name generation failed for '%s': %s — 로컬 fallback 사용", korean_name, exc)
    # fallback: 한글 제거, 영숫자만
    import re
    return re.sub(r'[^a-zA-Z0-9 ]', '', korean_name).strip() or "Project"


def ensure_english_name(db, project_id: str) -> str:
    """name_en이 없으면 생성해서 DB 저장 후 반환."""
    project = db.query(ProjectRegistry).filter(ProjectRegistry.id == project_id).first()
    if not project:
        return "Project"
    if project.name_en:
        return project.name_en
    name_en = generate_english_name(project.name)
    project.name_en = name_en
    db.commit()
    return name_en


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()
        name_en = generate_english_name(name)

        project = ProjectRegistry(
            id=project_id,
            name=name,
            name_en=name_en,
            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
