import datetime
from pathlib import Path, PurePath
from typing import Any
import urllib.parse
import uuid

from pydantic import BaseModel, ConfigDict, Field

from filerohr.utils import now


class Metadata(BaseModel):
    model_config = ConfigDict(extra="allow")

    def merge[T](self: type[T], other: BaseModel | dict[str, Any], overwrite: bool = False) -> T:
        all_fields = set(dict(self).keys()) | set(dict(other).keys())
        self_set_fields = self.model_fields_set
        try:
            other_set_fields = other.model_fields_set
        except AttributeError:
            other_set_fields = set(other.keys())
        iter_fields = all_fields if overwrite else all_fields - self_set_fields
        iter_fields &= other_set_fields
        try:
            update = other.model_dump(include=iter_fields)
        except AttributeError:
            update = {key: other[key] for key in iter_fields}
        return self.model_copy(update=update)

    def __or__[T](self: type[T], other: BaseModel | dict[str, Any]) -> T:
        return self.merge(other)


class File(BaseModel):
    name: str
    path: Path | str
    id: uuid.uuid4 = Field(default_factory=uuid.uuid4)
    hash: str | None = None
    mime_type: str | None = None
    created_at: datetime.datetime = Field(default_factory=now)
    metadata: Metadata = Field(default_factory=Metadata)
    keep: bool = False
    log: str = ""

    def clone(self, cls: type["File"] | None = None, **kwargs):
        if cls is None:
            cls = type(self)
        new_attrs = {"keep": False} if "path" in kwargs else {}
        new_attrs.update(kwargs)
        data = self.model_copy(deep=True).model_dump()
        data.update(new_attrs)
        del data["id"]
        if "path" in kwargs and "hash" not in kwargs:
            del data["hash"]
        return cls(**data)

    @classmethod
    def from_path(cls, path: Path | str, **kwargs):
        path_str = str(path)
        url = urllib.parse.urlparse(path_str)
        if url.scheme:
            return cls.from_url(path_str, **kwargs)
        return cls.from_filesystem(Path(path), **kwargs)

    @staticmethod
    def from_filesystem(path: Path, **kwargs):
        kwargs.setdefault("name", path.stem)
        return LocalFile(path=path, **kwargs)

    @staticmethod
    def from_url(url: str, **kwargs):
        kwargs.setdefault("name", PurePath(urllib.parse.urlparse(url).path).name)
        return RemoteFile(path=url, **kwargs)


class RemoteFile(File):
    path: str


class LocalFile(File):
    path: Path


class AVMetadata(Metadata):
    duration: datetime.timedelta | None = Field(default=None)


class LocalAVFile(LocalFile):
    metadata: AVMetadata = Field(default_factory=AVMetadata)


class AudioMetadata(AVMetadata):
    album: str = ""
    artist: str = ""
    isrc: str = ""
    organization: str = ""
    title: str = ""
    date: str = ""
    genre: str = ""


class LocalAudioFile(LocalAVFile):
    metadata: AudioMetadata = Field(default_factory=AudioMetadata)
