from sqlalchemy import func, or_, select
from sqlalchemy.orm import Session, joinedload

from app.models.knowledge import (
    KnowledgeDevice,
    KnowledgeDocument,
    KnowledgeManufacturer,
    KnowledgeNote,
)


def paginate(query, limit: int, offset: int):
    return query.limit(max(1, min(limit, 100))).offset(max(0, offset))


class KnowledgeRepository:
    @staticmethod
    def list_manufacturers(db: Session, *, q: str = "", limit: int = 50, offset: int = 0) -> list[KnowledgeManufacturer]:
        query = select(KnowledgeManufacturer).order_by(KnowledgeManufacturer.name)
        if q:
            pattern = f"%{q.lower()}%"
            query = query.where(func.lower(KnowledgeManufacturer.name).like(pattern))
        return list(db.scalars(paginate(query, limit, offset)))

    @staticmethod
    def get_manufacturer(db: Session, manufacturer_id: int) -> KnowledgeManufacturer | None:
        return db.get(KnowledgeManufacturer, manufacturer_id)

    @staticmethod
    def get_manufacturer_by_name(db: Session, name: str, exclude_id: int | None = None) -> KnowledgeManufacturer | None:
        query = select(KnowledgeManufacturer).where(func.lower(KnowledgeManufacturer.name) == name.lower())
        if exclude_id is not None:
            query = query.where(KnowledgeManufacturer.id != exclude_id)
        return db.scalar(query)

    @staticmethod
    def get_manufacturer_by_slug(db: Session, slug: str, exclude_id: int | None = None) -> KnowledgeManufacturer | None:
        query = select(KnowledgeManufacturer).where(KnowledgeManufacturer.slug == slug)
        if exclude_id is not None:
            query = query.where(KnowledgeManufacturer.id != exclude_id)
        return db.scalar(query)

    @staticmethod
    def list_devices(
        db: Session,
        *,
        q: str = "",
        manufacturer_id: int | None = None,
        device_type: str = "",
        limit: int = 50,
        offset: int = 0,
    ) -> list[KnowledgeDevice]:
        query = select(KnowledgeDevice).options(joinedload(KnowledgeDevice.manufacturer)).order_by(KnowledgeDevice.name)
        if manufacturer_id is not None:
            query = query.where(KnowledgeDevice.manufacturer_id == manufacturer_id)
        if device_type:
            query = query.where(KnowledgeDevice.device_type == device_type)
        if q:
            pattern = f"%{q.lower()}%"
            query = query.where(
                or_(
                    func.lower(KnowledgeDevice.name).like(pattern),
                    func.lower(KnowledgeDevice.model_number).like(pattern),
                )
            )
        return list(db.scalars(paginate(query, limit, offset)))

    @staticmethod
    def get_device(db: Session, device_id: int) -> KnowledgeDevice | None:
        return db.scalar(
            select(KnowledgeDevice)
            .where(KnowledgeDevice.id == device_id)
            .options(joinedload(KnowledgeDevice.manufacturer))
        )

    @staticmethod
    def get_device_by_slug(
        db: Session,
        *,
        manufacturer_id: int,
        slug: str,
        exclude_id: int | None = None,
    ) -> KnowledgeDevice | None:
        query = select(KnowledgeDevice).where(KnowledgeDevice.manufacturer_id == manufacturer_id, KnowledgeDevice.slug == slug)
        if exclude_id is not None:
            query = query.where(KnowledgeDevice.id != exclude_id)
        return db.scalar(query)

    @staticmethod
    def list_documents(
        db: Session,
        *,
        q: str = "",
        manufacturer_id: int | None = None,
        device_id: int | None = None,
        document_type: str = "",
        language: str = "",
        tag: str = "",
        limit: int = 50,
        offset: int = 0,
    ) -> list[KnowledgeDocument]:
        query = (
            select(KnowledgeDocument)
            .options(joinedload(KnowledgeDocument.manufacturer), joinedload(KnowledgeDocument.device).joinedload(KnowledgeDevice.manufacturer))
            .order_by(KnowledgeDocument.created_at.desc())
        )
        if manufacturer_id is not None:
            query = query.where(KnowledgeDocument.manufacturer_id == manufacturer_id)
        if device_id is not None:
            query = query.where(KnowledgeDocument.device_id == device_id)
        if document_type:
            query = query.where(KnowledgeDocument.document_type == document_type)
        if language:
            query = query.where(KnowledgeDocument.language == language)
        if q:
            pattern = f"%{q.lower()}%"
            query = query.where(func.lower(KnowledgeDocument.title).like(pattern))
        documents = list(db.scalars(paginate(query, limit, offset)))
        if tag:
            tag_lower = tag.lower()
            documents = [document for document in documents if any(item.lower() == tag_lower for item in document.tags)]
        return documents

    @staticmethod
    def get_document(db: Session, document_id: int) -> KnowledgeDocument | None:
        return db.scalar(
            select(KnowledgeDocument)
            .where(KnowledgeDocument.id == document_id)
            .options(joinedload(KnowledgeDocument.manufacturer), joinedload(KnowledgeDocument.device).joinedload(KnowledgeDevice.manufacturer))
        )

    @staticmethod
    def get_document_by_slug(db: Session, slug: str, exclude_id: int | None = None) -> KnowledgeDocument | None:
        query = select(KnowledgeDocument).where(KnowledgeDocument.slug == slug)
        if exclude_id is not None:
            query = query.where(KnowledgeDocument.id != exclude_id)
        return db.scalar(query)

    @staticmethod
    def list_notes(
        db: Session,
        *,
        q: str = "",
        manufacturer_id: int | None = None,
        device_id: int | None = None,
        note_type: str = "",
        tag: str = "",
        limit: int = 50,
        offset: int = 0,
    ) -> list[KnowledgeNote]:
        query = (
            select(KnowledgeNote)
            .options(joinedload(KnowledgeNote.manufacturer), joinedload(KnowledgeNote.device).joinedload(KnowledgeDevice.manufacturer))
            .order_by(KnowledgeNote.created_at.desc())
        )
        if manufacturer_id is not None:
            query = query.where(KnowledgeNote.manufacturer_id == manufacturer_id)
        if device_id is not None:
            query = query.where(KnowledgeNote.device_id == device_id)
        if note_type:
            query = query.where(KnowledgeNote.note_type == note_type)
        if q:
            pattern = f"%{q.lower()}%"
            query = query.where(or_(func.lower(KnowledgeNote.title).like(pattern), func.lower(KnowledgeNote.content).like(pattern)))
        notes = list(db.scalars(paginate(query, limit, offset)))
        if tag:
            tag_lower = tag.lower()
            notes = [note for note in notes if any(item.lower() == tag_lower for item in note.tags)]
        return notes

    @staticmethod
    def get_note(db: Session, note_id: int) -> KnowledgeNote | None:
        return db.scalar(
            select(KnowledgeNote)
            .where(KnowledgeNote.id == note_id)
            .options(joinedload(KnowledgeNote.manufacturer), joinedload(KnowledgeNote.device).joinedload(KnowledgeDevice.manufacturer))
        )
