"""M2: prosty resolver studia. Ścieżka 1: exact external ref → update. Ścieżka 2: name_normalized exact match → reuse + dopnij external_ref. Ścieżka 3: insert nowego. (Fuzzy alias matching dochodzi w M3.) """ from __future__ import annotations import logging import uuid from sqlalchemy import func, select from sqlalchemy.orm import Session from app.models.studio import Studio, StudioAlias, StudioExternalRef from app.normalize.scenes import NormalizedStudio from app.normalize.text import slugify log = logging.getLogger(__name__) def _compact_name_twin(session: Session, name_normalized: str) -> Studio | None: """Jedno studio o tej samej nazwie bez spacji ("evilangel" = "evil angel"). xtapes podaje studia sklejone ("[EvilAngel]", "[TeamSkeet]"), a reszta zrodel ze spacja; osobne studio psulo dopasowanie filmow (studio "rozne" obnizalo ocene, np. "Hardcore Gangbangs 8" i "... VIII", zgloszenie f8c9486e). Tylko przy jednym trafieniu. """ compact = (name_normalized or "").replace(" ", "") if len(compact) < 4: return None rows = session.execute( select(Studio) .where(func.replace(Studio.name_normalized, " ", "") == compact) .limit(2) ).scalars().all() return rows[0] if len(rows) == 1 else None def resolve_studio( session: Session, *, norm: NormalizedStudio, source_id: uuid.UUID, ) -> Studio: if norm.external_id: ref = session.execute( select(StudioExternalRef).where( StudioExternalRef.source_id == source_id, StudioExternalRef.external_id == norm.external_id, ) ).scalar_one_or_none() if ref is not None: studio = session.get(Studio, ref.studio_id) assert studio is not None _update_studio_fields(studio, norm) return studio studio = session.execute( select(Studio).where(Studio.name_normalized == norm.name_normalized) ).scalar_one_or_none() if studio is None: studio = _compact_name_twin(session, norm.name_normalized) if studio is None: studio = Studio( name=norm.name, name_normalized=norm.name_normalized, slug=_unique_slug(session, norm.slug or slugify(norm.name) or "studio"), network=norm.network, homepage_url=norm.homepage_url, ) session.add(studio) session.flush() log.debug("studio create id=%s name=%s", studio.id, studio.name) else: _update_studio_fields(studio, norm) if norm.external_id: existing_ref = session.execute( select(StudioExternalRef).where( StudioExternalRef.source_id == source_id, StudioExternalRef.external_id == norm.external_id, ) ).scalar_one_or_none() if existing_ref is None: session.add( StudioExternalRef( source_id=source_id, external_id=norm.external_id, studio_id=studio.id, confidence=1.0, ) ) return studio def _update_studio_fields(studio: Studio, norm: NormalizedStudio) -> None: if norm.network and not studio.network: studio.network = norm.network if norm.homepage_url and not studio.homepage_url: studio.homepage_url = norm.homepage_url def _unique_slug(session: Session, base: str) -> str: candidate = base n = 1 while session.execute(select(Studio.id).where(Studio.slug == candidate)).first(): n += 1 candidate = f"{base}-{n}" return candidate def add_alias_if_missing( session: Session, *, studio_id: uuid.UUID, alias: str, alias_normalized: str, source_id: uuid.UUID | None, ) -> None: existing = session.execute( select(StudioAlias).where( StudioAlias.studio_id == studio_id, StudioAlias.alias_normalized == alias_normalized, ) ).scalar_one_or_none() if existing is None: session.add( StudioAlias( studio_id=studio_id, alias=alias, alias_normalized=alias_normalized, source_id=source_id, ) )