from __future__ import annotations

from dataclasses import asdict
from datetime import datetime, timezone
from uuid import uuid4

from app.domain.editor import EditorSession, VariantChange
from app.domain.editor_builder import build_runtime_payload_from_changes
from app.repositories.directus.variant_changes import VariantChangeRepository
from app.repositories.native.editor_sessions import EditorSessionRepository
from app.schemas.auth import AuthenticatedUser
from app.schemas.editor import (
    BuildPreviewRequest,
    BuildPreviewResponse,
    EditorChangeListResponse,
    EditorChangeRead,
    EditorSessionCreateRequest,
    EditorSessionRead,
    EditorSessionUpdateRequest,
    SaveVariantChangesRequest,
)


class EditorService:
    """Application service for visual-editor workflows."""

    def __init__(
        self,
        change_repository: VariantChangeRepository | None = None,
        session_repository: EditorSessionRepository | None = None,
    ) -> None:
        self.change_repository = change_repository or VariantChangeRepository()
        self.session_repository = session_repository or EditorSessionRepository()

    async def create_session(
        self,
        request: EditorSessionCreateRequest,
        current_user: AuthenticatedUser,
    ) -> EditorSessionRead:
        session = EditorSession(
            variant_id=request.variant_id,
            mode=request.mode,
            base_url=request.base_url,
            actor_id=current_user.id,
            actor_email=current_user.email,
        )
        await self.session_repository.create(session)
        return EditorSessionRead.model_validate(asdict(session))

    async def get_session(self, session_id: str) -> EditorSessionRead | None:
        session = await self.session_repository.get(session_id)
        if not session:
            return None
        return EditorSessionRead.model_validate(asdict(session))

    async def update_session(
        self,
        session_id: str,
        request: EditorSessionUpdateRequest,
    ) -> EditorSessionRead | None:
        session = await self.session_repository.get(session_id)
        if not session:
            return None

        if request.status is not None:
            session.status = request.status
        if request.draft_changes is not None:
            session.draft_changes = request.draft_changes
        session.updated_at = datetime.now(timezone.utc)

        await self.session_repository.update(session)
        return EditorSessionRead.model_validate(asdict(session))

    async def delete_session(self, session_id: str) -> None:
        await self.session_repository.delete(session_id)

    async def list_changes(self, variant_id: str) -> EditorChangeListResponse:
        items = await self.change_repository.list(params={"filter[variant_id][_eq]": variant_id})
        mapped = [EditorChangeRead.model_validate(item) for item in items]
        return EditorChangeListResponse(items=mapped)

    async def list_changes_with_access_token(
        self,
        variant_id: str,
        access_token: str,
    ) -> EditorChangeListResponse:
        items = await self.change_repository.list(
            params={"filter[variant_id][_eq]": variant_id},
            access_token=access_token,
        )
        mapped = [EditorChangeRead.model_validate(item) for item in items]
        return EditorChangeListResponse(items=mapped)

    async def save_changes(
        self,
        variant_id: str,
        request: SaveVariantChangesRequest,
    ) -> EditorChangeListResponse:
        # Full-replace semantics: delete any existing changes not present in the request.
        existing = await self.change_repository.list(params={"filter[variant_id][_eq]": variant_id})
        incoming_ids = {item.id for item in request.items if item.id}
        for existing_item in existing:
            if str(existing_item["id"]) not in incoming_ids:
                await self.change_repository.delete(str(existing_item["id"]))

        saved_items: list[EditorChangeRead] = []
        for item in request.items:
            payload = item.model_dump()
            payload["variant_id"] = variant_id

            change_id = payload.pop("id", None)
            if change_id:
                saved = await self.change_repository.update(change_id, payload)
            else:
                payload["id"] = str(uuid4())
                saved = await self.change_repository.create(payload)

            saved_items.append(EditorChangeRead.model_validate(saved))

        return EditorChangeListResponse(items=saved_items)

    async def save_changes_with_access_token(
        self,
        variant_id: str,
        request: SaveVariantChangesRequest,
        access_token: str,
    ) -> EditorChangeListResponse:
        # Full-replace semantics: delete any existing changes not present in the request.
        existing = await self.change_repository.list(
            params={"filter[variant_id][_eq]": variant_id},
            access_token=access_token,
        )
        incoming_ids = {item.id for item in request.items if item.id}
        for existing_item in existing:
            if str(existing_item["id"]) not in incoming_ids:
                await self.change_repository.delete(str(existing_item["id"]), access_token=access_token)

        saved_items: list[EditorChangeRead] = []
        for item in request.items:
            payload = item.model_dump()
            payload["variant_id"] = variant_id

            change_id = payload.pop("id", None)
            if change_id:
                saved = await self.change_repository.update(change_id, payload, access_token=access_token)
            else:
                payload["id"] = str(uuid4())
                saved = await self.change_repository.create(payload, access_token=access_token)

            saved_items.append(EditorChangeRead.model_validate(saved))

        return EditorChangeListResponse(items=saved_items)

    async def build_preview(self, request: BuildPreviewRequest) -> BuildPreviewResponse:
        changes = [
            VariantChange(
                id=item.id or str(uuid4()),
                variant_id=request.variant_id,
                change_type=item.change_type,
                selector_type=item.selector_type,
                selector_value=item.selector_value,
                sort_order=item.sort_order,
                payload=item.payload,
            )
            for item in request.items
        ]
        preview = build_runtime_payload_from_changes(request.variant_id, changes)
        return BuildPreviewResponse(
            variant_id=preview.variant_id,
            generated_at=preview.generated_at,
            operations=[asdict(operation) for operation in preview.operations],
        )
