from typing import List
from http import HTTPStatus

from pydantic import BaseModel
from pydantic import Field
from pydantic import field_validator
from pydantic import ValidationError

from .base import Crawler
from .base import Torrent
from .base import Status
from .base import User
from .base import Task
from .base import convert_to_bytes
from .base import Promotion
from .exceptions import RequestException

class Peers(BaseModel):
    upload: int
    download: int

class TorrentStatus(BaseModel):
    download_ratio: float = Field(alias='downCoefficient')
    upload_ratio: float = Field(alias='upCoefficient')

class TaskItem(BaseModel):
    torrent_id: str = Field(alias='tid', coerce_numbers_to_str=True)
    torrent_name: str = Field(alias='title')

class TaskData(BaseModel):
    items: List[TaskItem] = Field(alias='list')
    total_pages: int = Field(alias='maxPage')

class ListTaskResponse(BaseModel):
    data: TaskData

class TorrentItem(BaseModel):
    torrent_id: str = Field(alias='tid', coerce_numbers_to_str=True)
    torrent_name: str = Field(alias='title')
    size: str = Field(alias='fileSize')
    peers: Peers
    promotion: TorrentStatus = Field(alias='status')

    @field_validator('size')
    def size_validator(cls, value: str) -> int:
        result = convert_to_bytes(value)
        if not result:
            raise ValueError(f'Invalid size {value}')
        return result

    @field_validator('promotion')
    def promotion_validator(cls, value: TorrentStatus) -> Promotion:
        return Promotion(upload_ratio=value.upload_ratio, download_ratio=value.download_ratio)

class TorrentList(BaseModel):
    items: List[TorrentItem] = Field(alias='list')

class ListTorrentsResponse(BaseModel):
    data: TorrentList

class UserData(BaseModel):
    upload_bytes: int = Field(alias='upload')
    download_bytes: int = Field(alias='download')
    user_id: str = Field(alias='uid', coerce_numbers_to_str=True)
    user_name: str = Field(alias='username')
    bonus: float = Field(alias='point')
    passkey: str

class GetUserResponse(BaseModel):
    data: UserData

class GetTorrentData(BaseModel):
    torrent: TorrentItem

class GetTorrentResponse(BaseModel):
    data: GetTorrentData

class FSM(Crawler):
    base_url: str = 'https://api.fsm.name'

    def get_torrents(self, pages: int = 1) -> List[Torrent]:
        torrents = []

        for page in range(pages):
            response = self.session.get(
                url=self.base_url + '/Torrents/listTorrents',
                params={
                    'type': '0',
                    'systematics': '0',
                    'tags': '[]',
                    'keyword': '',
                    'page': str(page + 1)
                }
            )

            if not response.status_code == HTTPStatus.OK:
                raise RequestException(response)

            list_torrents_response = ListTorrentsResponse.parse_obj(response.json())
            for item in list_torrents_response.data.items:
                try:
                    torrent = Torrent(
                        torrent_id=item.torrent_id,
                        torrent_name=item.torrent_name,
                        size=item.size,
                        hit_and_run=False,
                        promotion=item.promotion,
                        seeders=item.peers.upload,
                        leechers=item.peers.download,
                        crawler=self
                    )
                except ValidationError as exception:
                    self.logger.warning(exception)
                    continue
                else:
                    torrents.append(torrent)

        return torrents

    def get_user(self) -> User:
        response = self.session.get(self.base_url + '/Users/infos')
        if not response.status_code == HTTPStatus.OK:
            raise RequestException(response)

        get_user_response: GetUserResponse = GetUserResponse.parse_obj(response.json())

        return User(
            user_id=get_user_response.data.user_id,
            user_name=get_user_response.data.user_name,
            email=None,
            upload_bytes=get_user_response.data.upload_bytes,
            download_bytes=get_user_response.data.download_bytes,
            bonus=get_user_response.data.bonus,
            passkey=get_user_response.data.passkey
        )

    def get_torrent(self, torrent_id: str) -> Torrent:
        response = self.session.get(
            url=self.base_url + '/Torrents/details',
            params={'tid': torrent_id}
        )

        if not response.status_code == HTTPStatus.OK:
            raise RequestException(response)

        get_torrent_response: GetTorrentResponse = GetTorrentResponse.parse_obj(response.json())
        item = get_torrent_response.data.torrent

        return Torrent(
            torrent_id=item.torrent_id,
            torrent_name=item.torrent_name,
            size=item.size,
            hit_and_run=False,
            promotion=item.promotion,
            seeders=item.peers.upload,
            leechers=item.peers.download,
            crawler=self
        )

    def download_torrent(self, torrent_id: str, file_path: str) -> bool:
        user = self.get_user()

        response = self.session.get(
            url=self.base_url + '/Torrents/download',
            params={'passkey': user.passkey, 'tid': torrent_id, 'source': 'direct'}
        )

        if not response.status_code == HTTPStatus.OK:
            self.logger.warning(RequestException(response))
            return False

        with open(file_path, 'wb') as file:
            file.write(response.content)
            return True

    def get_tasks(self) -> List[Task]:
        page = 1
        tasks = []
        for api_path, status in [('/Torrents/listMyDownload', Status.LEECHING), ('/Torrents/listMySeed', Status.SEEDING)]:
            while True:
                response = self.session.get(self.base_url + api_path, params={'page': str(page)})

                if not response.status_code == HTTPStatus.OK:
                    self.logger.warning(RequestException(response))

                list_tasks_response: ListTaskResponse = ListTaskResponse.parse_obj(response.json())

                for item in list_tasks_response.data.items:
                    tasks.append(Task(torrent_id=item.torrent_id, torrent_name=item.torrent_name, status=status))

                if page >= list_tasks_response.data.total_pages:
                    break

                page += 1
        return tasks
