from typing import Optional, List

from sqlalchemy import null, func

import mindsdb.interfaces.storage.db as db
from mindsdb.utilities.context import context as ctx
import mindsdb.utilities.profiler as profiler


class PredictorRecordNotFound(Exception):
    def __init__(self, **kwargs):
        name = kwargs.get("name") or "-"
        predictor_id = kwargs.get("id") or "-"
        super().__init__(f"Predictor not found: name='{name}' id='{predictor_id}'")


class MultiplePredictorRecordsFound(Exception):
    def __init__(self, **kwargs):
        name = kwargs.get("name") or "-"
        predictor_id = kwargs.get("id") or "-"
        super().__init__(f"Found multiple predictor with: name='{name}' id='{predictor_id}'")


@profiler.profile()
def get_integration_record(name: str) -> db.Integration:
    company_id = ctx.company_id
    query = db.session.query(db.Integration).filter_by(company_id=company_id, name=name)
    if ctx.enforce_user_id:
        query = query.filter(db.Integration.user_id == ctx.user_id)
    record = query.first()
    return record


@profiler.profile()
def get_project_record(name: str) -> db.Project:
    company_id = ctx.company_id
    filters = [
        (func.lower(db.Project.name) == name),
        (db.Project.company_id == company_id),
        (db.Project.deleted_at == null()),
    ]
    if ctx.enforce_user_id:
        filters.append(db.Project.user_id == ctx.user_id)

    project_record = db.session.query(db.Project).filter(*filters).first()
    return project_record


@profiler.profile()
def get_project_records() -> List[db.Project]:
    company_id = ctx.company_id
    filters = [(db.Project.company_id == company_id), (db.Project.deleted_at == null())]
    if ctx.enforce_user_id:
        filters.append(db.Project.user_id == ctx.user_id)
    return db.session.query(db.Project).filter(*filters).all()


@profiler.profile()
def get_predictor_integration(record: db.Predictor) -> db.Integration:
    integration_record = db.session.query(db.Integration).filter_by(id=record.integration_id).first()
    return integration_record


@profiler.profile()
def get_predictor_project(record: db.Predictor) -> db.Project:
    project_record = db.session.query(db.Project).filter_by(id=record.project_id).first()
    return project_record


@profiler.profile()
def get_model_records(
    integration_id=None,
    active=True,
    deleted_at=null(),
    project_name: Optional[str] = None,
    ml_handler_name: Optional[str] = None,
    **kwargs,
):
    kwargs["company_id"] = ctx.company_id
    if ctx.enforce_user_id:
        kwargs["user_id"] = ctx.user_id

    if deleted_at is not None:
        kwargs["deleted_at"] = deleted_at
    if active is not None:
        kwargs["active"] = active

    if project_name is not None:
        project_record = get_project_record(name=project_name)
        if project_record is None:
            return []
        kwargs["project_id"] = project_record.id

    if ml_handler_name is not None:
        ml_handler_record = get_integration_record(name=ml_handler_name)
        if ml_handler_record is None:
            # raise Exception(f'unknown ml handler: {ml_handler_name}')
            return []
        kwargs["integration_id"] = ml_handler_record.id

    if integration_id is not None:
        kwargs["integration_id"] = integration_id

    return db.session.query(db.Predictor).filter_by(**kwargs).all()


@profiler.profile()
def get_model_record(
    except_absent=False,
    ml_handler_name: Optional[str] = None,
    project_name: Optional[str] = None,
    active: bool = True,
    deleted_at=null(),
    version: Optional[int] = None,
    **kwargs,
):
    kwargs["company_id"] = ctx.company_id
    if ctx.enforce_user_id:
        kwargs["user_id"] = ctx.user_id

    kwargs["deleted_at"] = deleted_at
    if active is not None:
        kwargs["active"] = active
    if version is not None:
        kwargs["version"] = version

    if project_name is not None:
        project_record = get_project_record(name=project_name)
        if project_record is None:
            return None
        kwargs["project_id"] = project_record.id

    if ml_handler_name is not None:
        ml_handler_record = get_integration_record(name=ml_handler_name)
        if ml_handler_record is None:
            # raise Exception(f'unknown ml handler: {ml_handler_name}')
            return []
        kwargs["integration_id"] = ml_handler_record.id

    records = db.session.query(db.Predictor).filter_by(**kwargs).all()
    if len(records) > 1:
        raise MultiplePredictorRecordsFound(**kwargs)
    if len(records) == 0:
        if except_absent is True:
            raise PredictorRecordNotFound(**kwargs)
        else:
            return None
    return records[0]
