from __future__ import annotations

from datetime import datetime
from typing import Any

from fastapi import HTTPException
from sqlmodel import Session

from app.models.core import ActivityEvent, CreativeChange, MetadataVersion, PostDraft, VideoAsset, now_utc

FIELD_TO_CHANGE_TYPE = {
    "title": "title",
    "description": "description",
    "caption": "caption",
    "hashtags_json": "hashtags",
    "tags_json": "hashtags",
    "scheduled_at": "schedule",
}

ALLOWED_DRAFT_FIELDS = {"title", "description", "caption", "hashtags_json", "tags_json", "category_id", "privacy_status", "scheduled_at", "metadata_json", "platform_account_id", "status"}


def parse_optional_datetime(value):
    if value in (None, ""):
        return None
    if isinstance(value, datetime):
        return value
    if isinstance(value, str):
        normalized = value.replace("Z", "+00:00")
        try:
            return datetime.fromisoformat(normalized)
        except ValueError as exc:
            raise HTTPException(400, "scheduled_at must be ISO datetime") from exc
    raise HTTPException(400, "scheduled_at must be ISO datetime")


def _text(value: Any) -> str | None:
    if value in (None, ""):
        return None
    if isinstance(value, list):
        return " ".join(str(item) for item in value)
    if isinstance(value, datetime):
        return value.isoformat()
    return str(value)


def add_activity(session: Session, entity_type: str, entity_id: str, event_type: str, label: str, payload: dict | None = None, created_by: str = "creator"):
    session.add(ActivityEvent(
        entity_type=entity_type,
        entity_id=entity_id,
        event_type=event_type,
        label=label,
        payload_json=payload or {},
        created_by=created_by,
    ))


def add_creative_changes(session: Session, draft: PostDraft, diff: dict, payload: dict):
    change_note = payload.get("change_note") or payload.get("changeNote")
    hypothesis = payload.get("hypothesis")
    created_by = payload.get("edited_by") or payload.get("created_by") or "creator"
    for field, change in diff.items():
        change_type = FIELD_TO_CHANGE_TYPE.get(field)
        if not change_type:
            continue
        session.add(CreativeChange(
            video_asset_id=draft.video_asset_id,
            post_draft_id=draft.id,
            topic_id=None,
            change_type=change_type,
            before_text=_text(change.get("before")),
            after_text=_text(change.get("after")),
            change_note=change_note or f"{change_type.title()} edited on review page",
            hypothesis=hypothesis,
            created_by=created_by,
            source="metadata_version",
        ))
    if change_note and not any(FIELD_TO_CHANGE_TYPE.get(field) for field in diff):
        session.add(CreativeChange(
            video_asset_id=draft.video_asset_id,
            post_draft_id=draft.id,
            change_type=payload.get("change_type") or "other",
            change_note=change_note,
            hypothesis=hypothesis,
            created_by=created_by,
            source="manual",
        ))


def apply_draft_patch(session: Session, draft: PostDraft, payload: dict, *, invalidate_uploads: bool = True) -> dict:
    before = draft.model_dump(mode="json")
    for key, value in payload.items():
        if key in ALLOWED_DRAFT_FIELDS:
            setattr(draft, key, parse_optional_datetime(value) if key == "scheduled_at" else value)
    draft.updated_at = now_utc()
    after = draft.model_dump(mode="json")
    diff = {key: {"before": before.get(key), "after": after.get(key)} for key in ALLOWED_DRAFT_FIELDS if before.get(key) != after.get(key)}
    session.add(draft)
    if diff:
        session.add(MetadataVersion(post_draft_id=draft.id, edited_by=payload.get("edited_by", "creator"), diff_json=diff, snapshot_json=after))
        add_creative_changes(session, draft, diff, payload)
        add_activity(session, "post_draft", draft.id, "metadata_saved", "Metadata saved", {"changed_fields": sorted(diff.keys())}, payload.get("edited_by", "creator"))
        video = session.get(VideoAsset, draft.video_asset_id)
        if video:
            add_activity(session, "video", video.id, "metadata_saved", "Metadata saved", {"post_draft_id": draft.id, "changed_fields": sorted(diff.keys())}, payload.get("edited_by", "creator"))
        if invalidate_uploads:
            from app.services.youtube_upload_orchestrator import invalidate_open_requests_for_draft
            invalidate_open_requests_for_draft(session, draft.id, reason="metadata changed after approval")
    return diff
