import logging
import os
from typing import Generator, TYPE_CHECKING

from flask import current_app

from .exceptions import NotFoundException
from .instructions import BrickInstructions
from .rebrickable_set_list import RebrickableSetList
if TYPE_CHECKING:
    from .rebrickable_set import RebrickableSet

logger = logging.getLogger(__name__)


# Lego sets instruction list
class BrickInstructionsList(object):
    all: dict[str, BrickInstructions]
    rejected_total: int
    sets: dict[str, list[BrickInstructions]]
    sets_total: int
    unknown_total: int

    def __init__(self, /, *, force=False):
        # Load instructions only if there is none already loaded
        all = getattr(self, 'all', None)

        if all is None or force:
            logger.info('Loading instructions file list')

            BrickInstructionsList.all = {}
            BrickInstructionsList.rejected_total = 0
            BrickInstructionsList.sets = {}
            BrickInstructionsList.sets_total = 0
            BrickInstructionsList.unknown_total = 0

            # Try to list the files in the instruction folder
            try:
                # Make a folder relative to static
                folder: str = os.path.join(
                    current_app.static_folder,  # type: ignore
                    current_app.config['INSTRUCTIONS_FOLDER'],
                )

                for file in os.scandir(folder):
                    instruction = BrickInstructions(file)

                    # Unconditionnally add to the list
                    BrickInstructionsList.all[instruction.filename] = instruction  # noqa: E501

                    if instruction.allowed:
                        if instruction.set:
                            # Instantiate the list if not existing yet
                            if instruction.set not in BrickInstructionsList.sets:  # noqa: E501
                                BrickInstructionsList.sets[instruction.set] = []  # noqa: E501

                            BrickInstructionsList.sets[instruction.set].append(instruction)  # noqa: E501
                            BrickInstructionsList.sets_total += 1
                        else:
                            BrickInstructionsList.unknown_total += 1
                    else:
                        BrickInstructionsList.rejected_total += 1

                # List of Rebrickable sets
                rebrickable_sets: dict[str, RebrickableSet] = {}
                for rebrickable_set in RebrickableSetList().all():
                    rebrickable_sets[rebrickable_set.fields.set] = rebrickable_set  # noqa: E501

                # Inject the brickset if it exists
                for instruction in self.all.values():
                    if (
                        instruction.allowed and
                        instruction.set is not None and
                        instruction.rebrickable is None and
                        instruction.set in rebrickable_sets
                    ):
                        instruction.rebrickable = rebrickable_sets[instruction.set]  # noqa: E501
            # Ignore errors
            except Exception:
                pass

    # Grab instructions for a set
    def get(self, set: str) -> list[BrickInstructions]:
        if set in self.sets:
            return self.sets[set]
        else:
            return []

    # Grab instructions for a file
    def get_file(self, name: str) -> BrickInstructions:
        if name not in self.all:
            raise NotFoundException('Instruction file {name} does not exist'.format(  # noqa: E501
                name=name
            ))

        return self.all[name]

    # List of all instruction files
    def list(self, /) -> Generator[BrickInstructions, None, None]:
        # Get the filenames and sort them
        filenames = list(self.all.keys())
        filenames.sort()

        # Return the files
        for filename in filenames:
            yield self.all[filename]