# src\ArquitectureLayer\Repo.py

from abc import ABC
from typing import Type, TypeVar, Generic, Optional
from sqlalchemy.orm import Session
from sqlalchemy import func
from datetime import datetime

from src.ArquitectureLayer.Mapper import Mapper_base  # Asegúrate de importar tu ABC base

TModelo = TypeVar("TModelo")
TDominio = TypeVar("TDominio")

class Repo_base(ABC, Generic[TModelo, TDominio]):
    def __init__(self, session: Session, modelo: type[TModelo], mapper: type[Mapper_base[TDominio, TModelo]]):
        self.session = session
        self.Modelo = modelo
        self.Mapper = mapper

    # ----------------------
    # ADD
    # ----------------------

    def add(self, dominio: TDominio, created_by: Optional[str] = None, notes: Optional[str] = None) -> str:
        data = self.Mapper.to_dict(dominio)
        data.update({
            "sys_created_by": created_by,
            "sys_notes": notes,
            "sys_version": 1
        })
        model = self.Modelo(**data)
        self.session.add(model)
        self.session.commit()
        return model.id

    def add_many(self, dominios: list[TDominio], created_by: Optional[str] = None, notes: Optional[str] = None) -> list[str]:
        ids = []
        for dominio in dominios:
            data = self.Mapper.to_dict(dominio)
            data.update({
                "sys_created_by": created_by,
                "sys_notes": notes,
                "sys_version": 1
            })
            model = self.Modelo(**data)
            self.session.add(model)
            ids.append(model.id)
        self.session.commit()
        return ids

    # ----------------------
    # GET
    # ----------------------

    def get_by_id(self, id_: str) -> Optional[TDominio]:
        model = self.session.query(self.Modelo).filter_by(id=id_, sys_deleted_at=None).first()
        return self.Mapper.from_model(model) if model else None

    def get_all(self) -> list[TDominio]:
        models = self.session.query(self.Modelo).filter_by(sys_deleted_at=None).all()
        return self.Mapper.from_model_list(models)

    def get_paginated(self, offset: int = 0, limit: int = 10) -> list[TDominio]:
        models = self.session.query(self.Modelo).filter_by(sys_deleted_at=None).offset(offset).limit(limit).all()
        return self.Mapper.from_model_list(models)

    def get_deleted(self) -> list[TDominio]:
        models = self.session.query(self.Modelo).filter(self.Modelo.sys_deleted_at.isnot(None)).all()
        return self.Mapper.from_model_list(models)

    # ----------------------
    # UPDATE
    # ----------------------

    def update(self, id_: str, new_data: dict, updated_by: Optional[str] = None, notes: Optional[str] = None) -> bool:
        model = self.session.query(self.Modelo).filter_by(id=id_, sys_deleted_at=None).first()
        if not model:
            return False

        for key, value in new_data.items():
            if hasattr(model, key):
                setattr(model, key, value)

        model.sys_updated_by = updated_by
        model.sys_notes = notes
        model.sys_version = (model.sys_version or 1) + 1
        self.session.commit()
        return True

    def bulk_update(self, updates: list[tuple[str, dict]], updated_by: Optional[str] = None, notes: Optional[str] = None) -> int:
        count = 0
        for id_, data in updates:
            if self.update(id_, data, updated_by=updated_by, notes=notes):
                count += 1
        return count

    # ----------------------
    # DELETE
    # ----------------------

    def delete_by_id(self, id_: str) -> bool:
        model = self.session.query(self.Modelo).filter_by(id=id_).first()
        if model:
            self.session.delete(model)
            self.session.commit()
            return True
        return False

    def delete_all(self) -> int:
        deleted = self.session.query(self.Modelo).delete()
        self.session.commit()
        return deleted

    # ----------------------
    # SOFT DELETE
    # ----------------------

    def soft_delete(self, id_: str, deleted_by: Optional[str] = None, notes: Optional[str] = None) -> bool:
        model = self.session.query(self.Modelo).filter_by(id=id_, sys_deleted_at=None).first()
        if model:
            model.sys_deleted_at = datetime.now()
            model.sys_updated_by = deleted_by
            model.sys_notes = notes
            model.sys_version = (model.sys_version or 1) + 1
            self.session.commit()
            return True
        return False

    def soft_restore(self, id_: str, restored_by: Optional[str] = None, notes: Optional[str] = None) -> bool:
        model = self.session.query(self.Modelo).filter_by(id=id_).first()
        if model and model.sys_deleted_at is not None:
            model.sys_deleted_at = None
            model.sys_updated_by = restored_by
            model.sys_notes = notes
            model.sys_version = (model.sys_version or 1) + 1
            self.session.commit()
            return True
        return False

    # ----------------------
    # OTROS
    # ----------------------

    def exists(self, id_: str) -> bool:
        return self.session.query(self.Modelo).filter_by(id=id_, sys_deleted_at=None).first() is not None

    def count(self) -> int:
        return self.session.query(self.Modelo).filter_by(sys_deleted_at=None).count()
