import json
import os
import re
from datetime import datetime
from functools import cached_property
from typing import Optional, Union, Any, List
from urllib.parse import urlparse

from pydantic import BaseModel, Field, field_validator, model_validator, ValidationError, computed_field, \
    field_serializer, Base64Bytes, RootModel
from pydantic.json_schema import JsonSchemaValue
from pydantic_core.core_schema import SerializationInfo

from ..enums.models import MediaProtocol, MediaCacheStatus, CacheOperationType
from ..settings.cluster import WorkerConfig
from ..ffmpeg_tasks.models import VideoMetadata

config = WorkerConfig()


class MediaSource(BaseModel):
    path: str = Field(description="媒体源的路径")
    protocol: MediaProtocol = Field(description="媒体源的来源协议")
    endpoint: Optional[str] = Field(description="媒体源来源的终端地址，根据使用协议的不同，会有没有endpoint的情况")
    bucket: Optional[str] = Field(description="媒体源所使用的存储桶(s3)/SubAppId(vod)")
    urn: Optional[str] = Field(description="媒体源的唯一指定标识")
    status: MediaCacheStatus = Field(default=MediaCacheStatus.unknown, description="媒体源在Modal集群挂载的缓存状态")
    expired_at: Optional[datetime] = Field(description="缓存过期时间点, 为None时不会过期（过期处理WIP）", default=None)
    downloader_id: Optional[str] = Field(description="正在处理下载的Downloader ID", default=None)
    progress: int = Field(description="缓存进度", default=0)
    metadata: Optional[VideoMetadata] = Field(description="媒体元数据", default=None)
    content_length: Optional[int] = Field(description="媒体文件大小", default=None)

    @classmethod
    def from_str(cls, media_url: str) -> 'MediaSource':
        if media_url.startswith('http://') or media_url.startswith('https://'):
            parsed_url = urlparse(media_url)
            path_str = f"{parsed_url.path}?{parsed_url.query}" if parsed_url.query else parsed_url.path
            return MediaSource(path=path_str,
                               protocol=MediaProtocol.http,
                               endpoint=parsed_url.netloc,  # domain of http url
                               bucket=None,
                               urn=media_url)
        elif media_url.startswith('s3://'):
            pattern = r"^s3://[a-z]{2}-[a-z]+-\d/.*$"
            if not re.match(pattern, media_url):
                media_url = media_url.replace("s3://", f"s3://{config.S3_region}/")
            # s3://{endpoint}/{bucket}/{url}
            paths = media_url[5:].split('/')

            if len(paths) < 3:
                raise ValidationError("URN-s3 格式错误")

            media_source = MediaSource(path='/'.join(paths[2:]),
                                       protocol=MediaProtocol.s3,
                                       endpoint=paths[0],
                                       bucket=paths[1],
                                       urn=media_url)

            s3_mount_path = config.S3_mount_dir
            cache_path = os.path.join(s3_mount_path, media_source.cache_filepath)

            # 校验媒体文件是否存在缓存中
            if media_source.status is MediaCacheStatus.unknown:
                if not os.path.exists(cache_path):
                    media_source.status = MediaCacheStatus.missing
                else:
                    media_source.status = MediaCacheStatus.ready

            return media_source

        elif media_url.startswith('vod://'):  # vod://{endpoint}/{subAppId}/{fileId}
            paths = media_url[6:].split('/')
            if len(paths) < 3:
                raise ValidationError("URN-vod 格式错误")
            # 兼容有文件类型后缀和没有文件类型后缀的格式
            url = paths[2] if '.' in os.path.basename(paths[2]) else paths[2] + ".mp4"
            return MediaSource(path=url,
                               protocol=MediaProtocol.vod,
                               bucket=paths[1],
                               endpoint=paths[0],
                               urn=media_url)
        elif media_url.startswith('cos://'):  # cos://{endpoint}/{bucket}/{url}
            paths = media_url[6:].split('/')
            return MediaSource(path='/'.join(paths[2:]),
                               protocol=MediaProtocol.cos,
                               endpoint=paths[0],
                               bucket=paths[1],
                               urn=media_url)
        elif media_url.startswith('hls://'):
            # hls://merge-local-1324682537.cos.ap-shanghai.myqcloud.com/streams/1264/30322.m3u8
            paths = media_url[6:]
            return MediaSource(path=f"https://{paths}",
                               protocol=MediaProtocol.hls,
                               endpoint=None,
                               bucket=None,
                               urn=media_url
                               )
        else:
            available_schemas = [member.value for member in MediaProtocol]
            available_schemas_str = ','.join(available_schemas)
            raise ValidationError(f"mediaUrl必须以{available_schemas_str}协议开头")

    @classmethod
    def __get_pydantic_json_schema__(cls, core_schema: Any, handler: Any) -> JsonSchemaValue:
        # Override the schema to represent it as a string
        return {
            "type": "string",
            "examples": [
                "vod://ap-shanghai/subAppId/fileId",
                "http://example.com/video.mp4",
                "s3://endpoint/bucket/path/to/file",
                "cos://endpoint/bucket/path/to/file"
            ]
        }

    @computed_field(description="s3挂载路径下的缓存相对路径")
    @property
    def cache_filepath(self) -> str:
        match self.protocol:
            case MediaProtocol.s3:
                # 本地挂载缓存
                if self.protocol == MediaProtocol.s3 and self.endpoint == config.S3_region and self.bucket == config.S3_bucket_name:
                    return f"{self.path}"
                else:
                    return f"{self.protocol.value}/{self.endpoint}/{self.bucket}/{self.path}"
            case MediaProtocol.http:
                clean_path = self.path.split('?')[0] if '?' in self.path else self.path
                # 移除路径开头的斜杠，避免双斜杠问题
                clean_path = clean_path.lstrip('/')
                return f"{self.protocol.value}/{self.endpoint}/{clean_path}"
            case _:
                return f"{self.protocol.value}/{self.endpoint}/{self.bucket}/{self.path}"

    @computed_field(description="文件后缀名")
    @cached_property
    def file_extension(self) -> Optional[str]:
        return os.path.basename(self.urn).split('.')[-1]

    @computed_field(description="是否本地可用")
    @property
    def local_available(self) -> bool:
        if self.status == MediaCacheStatus.ready:
            return self.local_exists
        return False

    @computed_field(description="是否存在本地文件")
    @property
    def local_exists(self) -> bool:
        return os.path.exists(self.local_mount_path)

    @computed_field(description="本地挂载地址")
    @property
    def local_mount_path(self) -> str:
        return f"{config.S3_mount_dir}/{self.cache_filepath}"

    @field_serializer('expired_at')
    def serialize_datetime(self, value: Optional[datetime], info: SerializationInfo) -> Optional[str]:
        if value:
            return value.isoformat()
        else:
            return None

    def get_cdn_url(self) -> str:
        if self.protocol == MediaProtocol.s3:
            return f"{self.path}"
        elif self.protocol == MediaProtocol.http:
            # 对于HTTP协议，使用cache_filepath确保路径一致性
            return self.cache_filepath
        return f"{self.protocol.value}/{self.endpoint}/{self.bucket}/{self.path}"

    @computed_field(description="CDN挂载地址")
    @property
    def url(self) -> str:
        return f"{config.S3_cdn_endpoint}/{self.get_cdn_url()}"

    def __str__(self):
        match self.protocol:
            case MediaProtocol.http:
                return self.urn
            case MediaProtocol.hls:
                return f"{self.protocol.value}://{self.path[8:]}"  # strip "https://" from url
            case _:
                return f"{self.protocol.value}://{self.endpoint}/{self.bucket}/{self.path}"


class CacheTask(BaseModel):
    type: CacheOperationType = Field(description="操作类型")
    source: MediaSource = Field(description="源媒体URN")
    target: Optional[MediaSource] = Field(description="目标媒体URN")

    @field_validator('source', mode='before')
    @classmethod
    def parse_source(cls, v: Union[str, MediaSource]) -> MediaSource:
        if isinstance(v, str):
            media_source = MediaSource.from_str(v)
            if media_source.protocol == MediaProtocol.s3:
                return media_source
            else:
                raise ValidationError('media只支持s3格式的urn')
        elif isinstance(v, MediaSource):
            return v
        else:
            raise ValidationError("media格式读取失败")

    @field_validator('target', mode='before')
    @classmethod
    def parse_target(cls, v: Union[str, MediaSource]) -> Optional[MediaSource]:
        if v is None:
            return None
        if isinstance(v, str):
            media_source = MediaSource.from_str(v)
            if media_source.protocol == MediaProtocol.s3:
                return media_source
            else:
                raise ValidationError('media只支持s3格式的urn')
        elif isinstance(v, MediaSource):
            return v
        else:
            raise ValidationError("media格式读取失败")

    @model_validator(mode="after")
    def parse_model(self):
        match self.type:
            case CacheOperationType.copy:
                if self.target is None:
                    raise ValidationError("使用copy行为时必填target URN")
            case CacheOperationType.move:
                if self.target is None:
                    raise ValidationError("使用move行为时必填target URN")
            case _:
                return self


class CacheTaskResult(CacheTask):
    success: bool = Field(default=False, description="执行成功")


class Base64File(BaseModel):
    raw_content: Base64Bytes = Field(description="Base64编码的原始内容")
    filename: str = Field(description="文件名, 包含文件类型后缀")
    content_type: str = Field(description="文件数据类型")

    @computed_field(description="文件字节大小")
    @property
    def size(self) -> int:
        return len(self.raw_content)


class LiveProduct(BaseModel):
    title: str = Field(default="", description="商品标题")
    leaf_category: str = Field(default="", description="商品分类")
    shop_id: int = Field(default=0, description="店铺ID")
    product_id: str = Field(default="", description="商品ID")
    cover: str = Field(default="", description="商品封面图链接")
    detail_url: str = Field(default="", description="商品详情链接")


class LiveProductCaches(BaseModel):
    room_id: str = Field(default="", description="直播间room_id/Room room_id")
    author_id: str = Field(default="", description="作者id/Author id")
    update_time: str = Field(default=0, description="商品列表更新时间")
    count: int = Field(default=0, description="缓存直播间商品数量")
    product_list: List[LiveProduct] = Field(default=None, description="缓存直播间商品列表")


class VisualFeatures(BaseModel):
    """
    代表产品的视觉特征
    """
    color: str = Field(
        description="商品详细颜色配色等色彩特征"
    )
    pattern: str = Field(
        description="商品详细图案纹理布料等材质特征, 材料可以根据产品名称和图像确定"
    )
    style: str = Field(
        default=None,
        description="商品详细款式风格设计等有辨识度的款式特征"
    )


class ProductInfo(BaseModel):
    """
    表示从图像中提取单个产品的信息
    """
    image_order: int = Field(
        description="图像出现的顺序"
    )
    image_name: str = Field(
        description="图像上显示的原始文本"
    )
    matched_product: str = Field(
        description="匹配的标准产品名称（必须与产品列表中的标准产品名称保持一致），或者如果没有匹配，则空"
    )
    match_confidence: int = Field(
        ge=0,
        le=100,
        description="产品匹配的置信度范围从0到100"
    )
    visual_features: VisualFeatures = Field(
        description="产品的详细视觉特征"
    )


class GeminiFirstStageResponseModel(RootModel):
    """
    从图像中提取的产品信息列表
    """
    root: List[ProductInfo]


class ProductTimeline(BaseModel):
    """
    表示单个商品及其在视频时间线中的出现信息。
    """
    product: str = Field(description="标准商品名称")
    timeline: List[str] = Field(
        description="该商品在视频中出现的时间段列表。每个字符串表示一个时间段，格式为 '开始时间 - 结束时间 (描述)'"
    )


class GeminiFirstStagePromptVariables(BaseModel):
    product_list: List[str] = Field(description="商品名列表")

    @computed_field(description="xml格式排列的商品列表")
    @property
    def product_list_xml(self) -> str:
        xml_items = [f"              <product>{product}</product>" for product in self.product_list]
        xml_string = "\n".join(xml_items)
        return f"<products>\n{xml_string}\n            </products>"


class GeminiSecondStagePromptVariables(BaseModel):
    product_json_list: List[dict] = Field(description="识别出的商品JSON列表")

    @computed_field(description="xml格式排列的识别出的商品JSON列表")
    @property
    def product_json_list_xml(self) -> str:
        xml_items = [f"              <product>{json.dumps(product, ensure_ascii=False)}</product>" for product in
                     self.product_json_list]
        xml_string = "\n".join(xml_items)
        return f"<products>\n{xml_string}\n            </products>"
