from __future__ import annotations

import datetime
import os
import uuid
from concurrent.futures import ThreadPoolExecutor
from time import ctime

from file import File
from folder import Folder
from request_client import RequestClient
from sqlmodel import Field, Session, SQLModel, create_engine, delete, select


class FolderStructure(Folder):
    files: list[File]
    folders: list[FolderStructure]

    def find_folder(self, name: str) -> File | None:
        for file in self.folders:
            if file.folder_name == name:
                return file

    def find_file(self, name: str) -> File | None:
        for file in self.files:
            if file.file_name == name:
                return file

    @staticmethod
    def get_structure(folder_id: uuid.UUID) -> FolderStructure:
        return FolderStructure.model_validate_json(
            RequestClient()
            .client.get("/folders/structure", params={"folder_id": folder_id})
            .text
        )


class SyncData(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    folder_id: uuid.UUID = Field(unique=True, index=True)
    path: str
    last_updated: datetime.datetime

    @staticmethod
    def sync_all():
        with Session(engine) as s:
            syncs = s.exec(select(SyncData)).fetchall()
        with ThreadPoolExecutor(3) as pool:
            map_ = pool.map(
                lambda sync: merge_folder(
                    sync.path,
                    FolderStructure.get_structure(sync.folder_id),
                    sync.last_updated,
                ),
                syncs,
            )
            tuple(map_)

    @staticmethod
    def new_sync(path: str, folder_id: uuid.UUID):
        with Session(engine) as s:
            sync = SyncData(
                path=path,
                last_updated=datetime.datetime.now(),
                folder_id=folder_id,
            )
            s.add(sync)
            s.commit()
        upload_folder(path, folder_id)

    @staticmethod
    def get_for_folder(folder_id: uuid.UUID) -> SyncData | None:
        with Session(engine) as s:
            return s.exec(
                select(SyncData).where(SyncData.folder_id == folder_id)
            ).one_or_none()

    @staticmethod
    def delete(folder_id: uuid.UUID):
        with Session(engine) as s:
            s.exec(delete(SyncData).where(SyncData.folder_id == folder_id))
            s.commit()


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)
SQLModel.metadata.create_all(engine)


def upload_folder(path: str, folder_id: uuid.UUID):
    items = os.listdir(path)
    for item in items:
        item_path = os.path.join(path, item)
        if os.path.isfile(item_path):
            File.create(item_path, folder_id)
        elif os.path.isdir(item_path):
            id = Folder.create(item, folder_id)
            upload_folder(item_path, id)


def merge_folder(
    path: str,
    structure: FolderStructure,
    time: datetime.datetime,
):
    for item in os.listdir(path):
        item_path = os.path.join(path, item)
        if os.path.isfile(item_path):
            file = structure.find_file(item)
            if file is None:
                File.create(item_path, structure.folder_id)
                continue
            mtime = datetime.datetime.strptime(
                ctime(os.path.getmtime(item_path)), "%a %b %d %H:%M:%S %Y"
            )
            print(item, mtime, time)
            if mtime > time:
                file.modify(item_path)
            else:
                file.download(item_path)
        elif os.path.isdir(item_path):
            folder = structure.find_folder(item)
            if folder is None:
                folder_id = Folder.create(item, structure.folder_id)
                upload_folder(item_path, folder_id)
            else:
                merge_folder(item_path, folder, time)
