import re
import time
from typing import Optional

from sqlalchemy import Boolean, Column, Index, Integer, JSON, String, func, or_, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import Session

from app.db import db_query, db_update, get_id_column, Base, async_db_query
from app.schemas.types import MediaType


def _text_like(column, pattern: str, wildcard: bool = False):
    """构造跨数据库大小写不敏感的文本匹配条件。"""
    if wildcard:
        return column.ilike(pattern, escape='\\')
    return column.ilike(pattern)


class TransferHistory(Base):
    """
    整理记录
    """
    id = get_id_column()
    # 源路径
    src = Column(String, index=True)
    # 源存储
    src_storage = Column(String)
    # 源文件项
    src_fileitem = Column(JSON, default=dict)
    # 目标路径
    dest = Column(String)
    # 目标存储
    dest_storage = Column(String)
    # 目标文件项
    dest_fileitem = Column(JSON, default=dict)
    # 转移模式 move/copy/link...
    mode = Column(String)
    # 类型 电影/电视剧
    type = Column(String)
    # 二级分类
    category = Column(String)
    # 标题
    title = Column(String, index=True)
    # 年份
    year = Column(String)
    tmdbid = Column(Integer, index=True)
    imdbid = Column(String)
    tvdbid = Column(Integer)
    doubanid = Column(String)
    # Sxx
    seasons = Column(String)
    # Exx
    episodes = Column(String)
    # 海报
    image = Column(String)
    # 下载器
    downloader = Column(String)
    # 下载器hash
    download_hash = Column(String, index=True)
    # 转移成功状态
    status = Column(Boolean(), default=True)
    # 转移失败信息
    errmsg = Column(String)
    # 时间
    date = Column(String)
    # 文件清单，以JSON存储
    files = Column(JSON, default=list)
    # 剧集组
    episode_group = Column(String)

    __table_args__ = (
        Index('ix_transferhistory_status_date', 'status', 'date'),
        Index('ix_transferhistory_date_id', 'date', 'id'),
    )

    @classmethod
    @db_query
    def list_by_title(cls, db: Session, title: str, page: Optional[int] = 1, count: Optional[int] = 30,
                      status: bool = None, wildcard: bool = False):
        if wildcard:
            text_filter = or_(
                _text_like(cls.title, title, wildcard=True),
                _text_like(cls.src, title, wildcard=True),
                _text_like(cls.dest, title, wildcard=True),
            )
        else:
            text_filter = or_(
                _text_like(cls.title, f'%{title}%'),
                _text_like(cls.src, f'%{title}%'),
                _text_like(cls.dest, f'%{title}%'),
            )
        query = db.query(cls).filter(text_filter)
        if status is not None:
            query = query.filter(cls.status == status)
        query = query.order_by(cls.date.desc())

        # 当count为负数时，不限制页数查询所有
        if count >= 0:
            query = query.offset((page - 1) * count).limit(count)

        return query.all()

    @classmethod
    @async_db_query
    async def async_list_by_title(cls, db: AsyncSession, title: str, page: Optional[int] = 1, count: Optional[int] = 30,
                                  status: bool = None, wildcard: bool = False):
        if wildcard:
            text_filter = or_(
                _text_like(cls.title, title, wildcard=True),
                _text_like(cls.src, title, wildcard=True),
                _text_like(cls.dest, title, wildcard=True),
            )
        else:
            text_filter = or_(
                _text_like(cls.title, f'%{title}%'),
                _text_like(cls.src, f'%{title}%'),
                _text_like(cls.dest, f'%{title}%'),
            )
        query = select(cls).filter(text_filter)
        if status is not None:
            query = query.filter(cls.status == status)
        query = query.order_by(cls.date.desc())

        # 当count为负数时，不限制页数查询所有
        if count >= 0:
            query = query.offset((page - 1) * count).limit(count)

        result = await db.execute(query)
        return result.scalars().all()

    @classmethod
    @db_query
    def list_by_page(cls, db: Session, page: Optional[int] = 1, count: Optional[int] = 30, status: bool = None):
        if status is not None:
            query = db.query(cls).filter(
                cls.status == status
            ).order_by(
                cls.date.desc()
            )
        else:
            query = db.query(cls).order_by(
                cls.date.desc()
            )
        
        # 当count为负数时，不限制页数查询所有
        if count >= 0:
            query = query.offset((page - 1) * count).limit(count)
        
        return query.all()

    @classmethod
    @async_db_query
    async def async_list_by_page(cls, db: AsyncSession, page: Optional[int] = 1, count: Optional[int] = 30,
                                 status: bool = None):
        if status is not None:
            query = select(cls).filter(
                cls.status == status
            ).order_by(
                cls.date.desc()
            )
        else:
            query = select(cls).order_by(
                cls.date.desc()
            )
        
        # 当count为负数时，不限制页数查询所有
        if count >= 0:
            query = query.offset((page - 1) * count).limit(count)
        
        result = await db.execute(query)
        return result.scalars().all()

    @classmethod
    @db_query
    def get_by_hash(cls, db: Session, download_hash: str):
        return db.query(cls).filter(cls.download_hash == download_hash).first()

    @classmethod
    @db_query
    def get_by_src(cls, db: Session, src: str, storage: Optional[str] = None):
        if storage:
            return db.query(cls).filter(cls.src == src,
                                        cls.src_storage == storage).first()
        else:
            return db.query(cls).filter(cls.src == src).first()

    @classmethod
    @db_query
    def get_by_dest(cls, db: Session, dest: str):
        return db.query(cls).filter(cls.dest == dest).first()

    @classmethod
    @db_query
    def list_by_hash(cls, db: Session, download_hash: str):
        return db.query(cls).filter(cls.download_hash == download_hash).all()

    @classmethod
    @db_query
    def statistic(cls, db: Session, days: Optional[int] = 7):
        """
        统计最近days天的下载历史数量，按日期分组返回每日数量
        """
        sub_query = db.query(func.substr(cls.date, 1, 10).label('date'),
                             cls.id.label('id')).filter(
            cls.date >= time.strftime("%Y-%m-%d %H:%M:%S",
                                      time.localtime(time.time() - 86400 * days))).subquery()
        return db.query(sub_query.c.date, func.count(sub_query.c.id)).group_by(sub_query.c.date).all()

    @classmethod
    @db_query
    def monthly_media_statistics(cls, db: Session):
        """
        统计当月成功整理的电影、电视剧和剧集数量。

        电影和电视剧按媒体身份去重；剧集优先按历史记录中的集数字段计算，
        缺少集数时按单条成功整理记录计数。
        """
        month_prefix = time.strftime("%Y-%m-", time.localtime())
        histories = db.query(cls).filter(
            cls.status.is_(True),
            cls.date.like(f"{month_prefix}%"),
            cls.type.in_([MediaType.MOVIE.value, MediaType.TV.value]),
        ).all()
        movie_identities = set()
        tv_identities = set()
        episode_count = 0

        for history in histories:
            identity = (history.tmdbid or 0, history.title or "", history.year or "")
            if history.type == MediaType.MOVIE.value:
                movie_identities.add(identity)
                continue

            tv_identities.add(identity)
            episode_count += cls._history_episode_count(history)

        return len(movie_identities), len(tv_identities), episode_count

    @staticmethod
    def _history_episode_count(history: "TransferHistory") -> int:
        """从单条整理历史中估算成功入库的剧集数量。"""
        episode_numbers = [int(value) for value in re.findall(r"\d+", history.episodes or "")]
        if len(episode_numbers) >= 2 and "-" in (history.episodes or ""):
            return max(1, episode_numbers[-1] - episode_numbers[0] + 1)
        if episode_numbers:
            return len(set(episode_numbers))
        if isinstance(history.files, list) and history.files:
            return len(history.files)

        return 1

    @classmethod
    @async_db_query
    async def async_statistic(cls, db: AsyncSession, days: Optional[int] = 7):
        """
        统计最近days天的下载历史数量，按日期分组返回每日数量
        """
        sub_query = select(func.substr(cls.date, 1, 10).label('date'),
                           cls.id.label('id')).filter(
            cls.date >= time.strftime("%Y-%m-%d %H:%M:%S",
                                      time.localtime(time.time() - 86400 * days))).subquery()
        result = await db.execute(
            select(sub_query.c.date, func.count(sub_query.c.id)).group_by(sub_query.c.date)
        )
        return result.all()

    @classmethod
    @db_query
    def count(cls, db: Session, status: bool = None):
        if status is not None:
            return db.query(func.count(cls.id)).filter(cls.status == status).first()[0]
        else:
            return db.query(func.count(cls.id)).first()[0]

    @classmethod
    @async_db_query
    async def async_count(cls, db: AsyncSession, status: bool = None):
        if status is not None:
            result = await db.execute(
                select(func.count(cls.id)).filter(cls.status == status)
            )
        else:
            result = await db.execute(
                select(func.count(cls.id))
            )
        return result.scalar()

    @classmethod
    @db_query
    def count_by_title(cls, db: Session, title: str, status: bool = None, wildcard: bool = False):
        if wildcard:
            text_filter = or_(
                _text_like(cls.title, title, wildcard=True),
                _text_like(cls.src, title, wildcard=True),
                _text_like(cls.dest, title, wildcard=True),
            )
        else:
            text_filter = or_(
                _text_like(cls.title, f'%{title}%'),
                _text_like(cls.src, f'%{title}%'),
                _text_like(cls.dest, f'%{title}%'),
            )
        query = db.query(func.count(cls.id)).filter(text_filter)
        if status is not None:
            query = query.filter(cls.status == status)
        return query.first()[0]

    @classmethod
    @async_db_query
    async def async_count_by_title(cls, db: AsyncSession, title: str, status: bool = None, wildcard: bool = False):
        if wildcard:
            text_filter = or_(
                _text_like(cls.title, title, wildcard=True),
                _text_like(cls.src, title, wildcard=True),
                _text_like(cls.dest, title, wildcard=True),
            )
        else:
            text_filter = or_(
                _text_like(cls.title, f'%{title}%'),
                _text_like(cls.src, f'%{title}%'),
                _text_like(cls.dest, f'%{title}%'),
            )
        stmt = select(func.count(cls.id)).filter(text_filter)
        if status is not None:
            stmt = stmt.filter(cls.status == status)
        result = await db.execute(stmt)
        return result.scalar()

    @classmethod
    @db_query
    def list_by(cls, db: Session, mtype: Optional[str] = None, title: Optional[str] = None, year: Optional[str] = None,
                season: Optional[str] = None,
                episode: Optional[str] = None, tmdbid: Optional[int] = None, dest: Optional[str] = None):
        """
        据tmdbid、season、season_episode查询转移记录
        tmdbid + mtype 或 title + year 必输
        """
        # TMDBID + 类型
        if tmdbid and mtype:
            # 电视剧某季某集
            if season is not None and episode:
                return db.query(cls).filter(cls.tmdbid == tmdbid,
                                            cls.type == mtype,
                                            cls.seasons == season,
                                            cls.episodes == episode,
                                            cls.dest == dest).all()
            # 电视剧某季
            elif season is not None:
                return db.query(cls).filter(cls.tmdbid == tmdbid,
                                            cls.type == mtype,
                                            cls.seasons == season).all()
            else:
                if dest:
                    # 电影
                    return db.query(cls).filter(cls.tmdbid == tmdbid,
                                                cls.type == mtype,
                                                cls.dest == dest).all()
                else:
                    # 电视剧所有季集
                    return db.query(cls).filter(cls.tmdbid == tmdbid,
                                                cls.type == mtype).all()
        # 标题 + 年份
        elif title and year:
            # 电视剧某季某集
            if season is not None and episode:
                return db.query(cls).filter(cls.title == title,
                                            cls.year == year,
                                            cls.seasons == season,
                                            cls.episodes == episode,
                                            cls.dest == dest).all()
            # 电视剧某季
            elif season is not None:
                return db.query(cls).filter(cls.title == title,
                                            cls.year == year,
                                            cls.seasons == season).all()
            else:
                if dest:
                    # 电影
                    return db.query(cls).filter(cls.title == title,
                                                cls.year == year,
                                                cls.dest == dest).all()
                else:
                    # 电视剧所有季集
                    return db.query(cls).filter(cls.title == title,
                                                cls.year == year).all()
        # 类型 + 转移路径（emby webhook season无tmdbid场景）
        elif mtype and season is not None and dest:
            # 电视剧某季
            return db.query(cls).filter(cls.type == mtype,
                                        cls.seasons == season,
                                        cls.dest.like(f"{dest}%")).all()
        return []

    @classmethod
    @db_query
    def get_by_type_tmdbid(cls, db: Session, mtype: Optional[str] = None, tmdbid: Optional[int] = None):
        """
        据tmdbid、type查询转移记录
        """
        return db.query(cls).filter(cls.tmdbid == tmdbid,
                                    cls.type == mtype).first()

    @classmethod
    @db_update
    def update_download_hash(cls, db: Session, historyid: Optional[int] = None, download_hash: Optional[str] = None):
        db.query(cls).filter(cls.id == historyid).update(
            {
                "download_hash": download_hash
            }
        )

    @classmethod
    @db_query
    def list_by_date(cls, db: Session, date: str):
        """
        查询某时间之后的转移历史
        """
        return db.query(cls).filter(cls.date > date).order_by(cls.id.desc()).all()

    @classmethod
    @db_update
    def delete_before(
        cls,
        db: Session,
        before_time: str,
        limit: Optional[int] = 500,
    ) -> int:
        """
        分批删除指定时间之前的整理历史。
        """
        ids = [
            row[0]
            for row in db.query(cls.id)
            .filter(cls.date < before_time)
            .order_by(cls.id.asc())
            .limit(limit)
            .all()
        ]
        if not ids:
            return 0
        return (
            db.query(cls)
            .filter(cls.id.in_(ids))
            .delete(synchronize_session=False)
        )
