from sqlite3 import Row
from typing import Any, Self

from flask import current_app, url_for

from .exceptions import DatabaseException, NotFoundException
from .instructions import BrickInstructions
from .instructions_list import BrickInstructionsList
from .minifigure_list import BrickMinifigureList
from .part_list import BrickPartList
from .rebrickable_image import RebrickableImage
from .record import BrickRecord
from .sql import BrickSQL
from .theme_list import BrickThemeList


# Lego brick set
class BrickSet(BrickRecord):
    instructions: list[BrickInstructions]
    theme_name: str

    # Queries
    select_query: str = 'set/select'
    insert_query: str = 'set/insert'

    def __init__(
        self,
        /,
        record: Row | dict[str, Any] | None = None,
    ):
        super().__init__()

        # Placeholders
        self.theme_name = ''
        self.instructions = []

        # Ingest the record if it has one
        if record is not None:
            self.ingest(record)

            # Resolve the theme
            self.resolve_theme()

            # Check for the instructions
            self.resolve_instructions()

    # Delete a set
    def delete(self, /) -> None:
        database = BrickSQL()
        parameters = self.sql_parameters()

        # Delete the set
        database.execute('set/delete/set', parameters=parameters)

        # Delete the minifigures
        database.execute(
            'minifigure/delete/all_from_set', parameters=parameters)

        # Delete the parts
        database.execute(
            'part/delete/all_from_set', parameters=parameters)

        # Delete missing parts
        database.execute('missing/delete/all_from_set', parameters=parameters)

        # Commit to the database
        database.commit()

    # Minifigures
    def minifigures(self, /) -> BrickMinifigureList:
        return BrickMinifigureList().load(self)

    # Parts
    def parts(self, /) -> BrickPartList:
        return BrickPartList().load(self)

    # Add instructions to the set
    def resolve_instructions(self, /) -> None:
        if self.fields.set_num is not None:
            self.instructions = BrickInstructionsList().get(
                self.fields.set_num
            )

    # Add a theme to the set
    def resolve_theme(self, /) -> None:
        try:
            id = self.fields.theme_id
        except Exception:
            id = 0

        theme = BrickThemeList().get(id)
        self.theme_name = theme.name

    # Return a short form of the set
    def short(self, /) -> dict[str, Any]:
        return {
            'name': self.fields.name,
            'set_img_url': self.fields.set_img_url,
            'set_num': self.fields.set_num,
        }

    # Select a specific part (with a set and an id)
    def select_specific(self, u_id: str, /) -> Self:
        # Save the parameters to the fields
        self.fields.u_id = u_id

        # Load from database
        record = self.select()

        if record is None:
            raise NotFoundException(
                'Set with ID {id} was not found in the database'.format(
                    id=self.fields.u_id,
                ),
            )

        # Ingest the record
        self.ingest(record)

        # Resolve the theme
        self.resolve_theme()

        # Check for the instructions
        self.resolve_instructions()

        return self

    # Update a checked state
    def update_checked(self, name: str, status: bool, /) -> None:
        parameters = self.sql_parameters()
        parameters['status'] = status

        # Update the checked status
        rows, _ = BrickSQL().execute_and_commit(
            'set/update_checked',
            parameters=parameters,
            name=name,
        )

        if rows != 1:
            raise DatabaseException('Could not update the status {status} for set {number}'.format(  # noqa: E501
                status=name,
                number=self.fields.set_num,
            ))

    # Self url
    def url(self, /) -> str:
        return url_for('set.details', id=self.fields.u_id)

    # Deletion url
    def url_for_delete(self, /) -> str:
        return url_for('set.delete', id=self.fields.u_id)

    # Actual deletion url
    def url_for_do_delete(self, /) -> str:
        return url_for('set.do_delete', id=self.fields.u_id)

    # Compute the url for the set image
    def url_for_image(self, /) -> str:
        if not current_app.config['USE_REMOTE_IMAGES'].value:
            return RebrickableImage.static_url(
                self.fields.set_num,
                'SETS_FOLDER'
            )
        else:
            return self.fields.set_img_url

    # Compute the url for the set instructions
    def url_for_instructions(self, /) -> str:
        if len(self.instructions):
            return url_for(
                'set.details',
                id=self.fields.u_id,
                open_instructions=True
            )
        else:
            return ''

    # Check minifigure collected url
    def url_for_minifigures_collected(self, /) -> str:
        return url_for('set.minifigures_collected', id=self.fields.u_id)

    # Compute the url for the rebrickable page
    def url_for_rebrickable(self, /) -> str:
        if current_app.config['REBRICKABLE_LINKS'].value:
            try:
                return current_app.config['REBRICKABLE_LINK_SET_PATTERN'].value.format(  # noqa: E501
                    number=self.fields.set_num.lower(),
                )
            except Exception:
                pass

        return ''

    # Check set checked url
    def url_for_set_checked(self, /) -> str:
        return url_for('set.set_checked', id=self.fields.u_id)

    # Check set collected url
    def url_for_set_collected(self, /) -> str:
        return url_for('set.set_collected', id=self.fields.u_id)

    # Normalize from Rebrickable
    @staticmethod
    def from_rebrickable(data: dict[str, Any], /, **_) -> dict[str, Any]:
        return {
            'set_num': data['set_num'],
            'name': data['name'],
            'year': data['year'],
            'theme_id': data['theme_id'],
            'num_parts': data['num_parts'],
            'set_img_url': data['set_img_url'],
            'set_url': data['set_url'],
            'last_modified_dt': data['last_modified_dt'],
            'mini_col': False,
            'set_col': False,
            'set_check': False,
        }
