import re
from pathlib import Path
from queue import Queue
from typing import List

import sqlite_utils

from bulk_restore_tool.models import Archive
from bulk_restore_tool.models import FileMetadata
from bulk_restore_tool.models import FileSelection
from bulk_restore_tool.models import RestorableFile

RESTORABLE_SQL = """
    SELECT
        a.deviceId,
        a.archiveId,
        a.destinationName,
        a.storageNodeWebUrl,
        f.fileId,
        f.path,
        f.backupSetId,
        f.versionTimemillis,
        max(f.sourceLastModifiedMillis) as sourceLastModifiedMillis,
        f.sourceLengthBytes,
        f.sourceSHA256Checksum
    FROM file f
    JOIN archive a on (a.archiveId = f.archiveId)
    WHERE f.fileType != 'DIRECTORY'
    AND f.selected
    AND NOT f.completed
    GROUP BY f.path
"""


def save_archive_metadata_sqlite(
    archive: Archive,
    files: List[dict],
    target: Path,
    selection: FileSelection,
) -> Path:
    file_path = target / archive.db_filename
    db = sqlite_utils.Database(file_path)
    archive_table = db["archive"].create(  # type: ignore
        columns={name: field.type_ for name, field in Archive.__fields__.items()},
        pk="archiveId",
        if_not_exists=True,
    )
    file_table = db["file"].create(  # type: ignore
        columns={name: field.type_ for name, field in FileMetadata.__fields__.items()},
        pk=("archiveId", "fileId"),
        if_not_exists=True,
    )
    archive_table.upsert(archive.dict(), pk="archiveId")
    include_patterns = [re.compile(pat) for pat in selection.includePatterns]
    exclude_patterns = [re.compile(pat) for pat in selection.excludePatterns]
    for file in files:
        model = FileMetadata(**file, archiveId=archive.archiveId)

        # apply include patterns
        if include_patterns:
            for regex in include_patterns:
                if regex.match(model.path):
                    model.selected = True
                    model.excludeReason = None
        else:
            model.selected = True
            model.excludeReason = None

        # skip other filter logic if file isn't included
        if not model.selected:
            model.excludeReason = "Didn't match any include patterns."
            file_table.upsert(model.dict(), pk=("archiveId", "fileId"))
            continue

        # apply exclude patterns
        if exclude_patterns:
            for regex in exclude_patterns:
                if regex.match(model.path):
                    model.selected = False
                    model.excludeReason = f"Matched exclude pattern: {regex.pattern}"

        # apply backup set filter
        if selection.backupSetIds:
            if model.backupSetId not in selection.backupSetIds:
                model.selected = False
                model.excludeReason = (
                    f"Not in selected backupSetIds: {selection.backupSetIds}"
                )

        # apply deleted file age filter
        if selection.includeDeleted and selection.deletedAgeMillis is not None:
            if model.deleted and model.deletionTimeMillis < selection.deletedAgeMillis:
                model.selected = False
                model.excludeReason = (
                    f"Delete prior to maxDeletedAge: {selection.deletedMaxAge}"
                )

        file_table.upsert(model.dict(), pk=("archiveId", "fileId"))

    db.create_view("restorable", RESTORABLE_SQL, replace=True)
    return file_path


def restore_queue(db: sqlite_utils.Database, archive_id: str) -> Queue[RestorableFile]:
    queue: Queue[RestorableFile] = Queue()
    for file in db.query(
        """
        SELECT *
        FROM restorable
        WHERE archiveId = :archiveId
        ORDER BY sourceLengthBytes ASC
        """,
        {"archiveId": archive_id},
    ):
        queue.put(RestorableFile(**file))
    return queue
