"""
Module contains base application and request class.
"""
from typing import Optional
import aioredis
from aioredis import Redis
from app_common.handlers.schemas import initializeSchemas
from configs.config_common import DATABASE_NUMBER, RESPONSE_TIMEOUT
from configs.configs.configs.settings.classes import DBSetting, RedisStorageSetting
from crutches_on_wheels.cow.adaptors.adaptor import DBAdaptor
from crutches_on_wheels.cow.utils.check_connection import (
    checkConnectionToDB,
    checkConnectionToRedis,
    checkConnectionToSentinel,
)
from crutches_on_wheels.cow.web.application import BaseLunaApplicationCtx, LunaApplication
from crutches_on_wheels.cow.web.cmd_parser import ApplicationCMDParser
from crutches_on_wheels.cow.web.request import VLRequest
from db.context_attributes import RedisContext as AttributesDBContext
from db.context_events import DBContext as EventsDBContext
from db.context_faces import DBContext as FacesDBContext
from db.events_db_tools.models.config import DBConfig as EventsDBConfig
from db.events_db_tools.models.events_models import Base as EventsBase
from db.faces_db_tools.models.config import DBConfig as FacesDBConfig
from db.faces_db_tools.models.faces_models import Base as FacesBase
[docs]class MatcherBaseCtx(BaseLunaApplicationCtx):
    """Base luna-python-matcher context"""
    #: faces database adaptor
    facesDBAdaptor: Optional[DBAdaptor]
    #: events database adaptor
    eventsDBAdaptor: Optional[DBAdaptor]
    #: redis client
    redisClient: Optional[Redis]
    #: faces database context
    facesDBContext: FacesDBContext
    #: events database context
    eventsDBContext: EventsDBContext
    #: attributes database context
    attributesDBContext: AttributesDBContext 
[docs]class MatcherBaseApp(LunaApplication):
    """Base service application"""
    ctx: MatcherBaseCtx
    def __init__(self, *args, **kwargs):
        super().__init__(*args, requestClass=VLRequest, **kwargs)
        self.config["RESPONSE_TIMEOUT"] = RESPONSE_TIMEOUT
[docs]    def initContexts(self) -> None:
        """Initialize faces, events and attributes databases context"""
        self.ctx.facesDBContext = FacesDBContext(self.ctx.logger)
        self.ctx.eventsDBContext = EventsDBContext(self.ctx.logger)
        self.ctx.attributesDBContext = AttributesDBContext(client=self.ctx.redisClient, logger=self.ctx.logger) 
    @staticmethod
    def _getCMDArgParser(defaultPort: int, serviceConfig: "BasePythonServiceSettingsClass") -> ApplicationCMDParser:
        """
        Get command line arguments parser. Overloading `_getCMDArgParser` to get the service type.
        Returns:
            parser with options
        """
        argParser = ApplicationCMDParser(
            defaultPort=defaultPort, tagableSettings=serviceConfig.getServiceSettingNames()
        )
        argParser.argParser.add_argument(
            "--service-type", type=str, choices=["matcher", "proxy"], help='service type: one of "matcher" or "proxy"'
        )
        return argParser
[docs]    async def initialize(self):
        """Initialize application."""
        initializeSchemas(self.ctx.serviceConfig.platformLimits)
        await self.initDBContext()
        await super().initialize()
        await self.initRedisDB(self.ctx.serviceConfig.attributesDB)
        self.initContexts() 
[docs]    async def initRedisDB(self, redisDBSettings: RedisStorageSetting):
        """
        Initialize Redis client.
        Args:
            redisDBSettings: redis db settings
        """
        if not redisDBSettings.sentinel.sentinels:
            self.ctx.redisClient = await aioredis.create_redis_pool(
                address=f"redis://{redisDBSettings.host}:{redisDBSettings.port}",
                db=redisDBSettings.dbNumber,
                password=redisDBSettings.password or None,
            )
        else:
            sentinel = await aioredis.create_sentinel(
                redisDBSettings.sentinel.sentinels,
                db=redisDBSettings.dbNumber,
                password=redisDBSettings.password or None,
            )
            self.ctx.redisClient = sentinel.master_for(redisDBSettings.sentinel.masterName) 
[docs]    async def initDBContext(self):
        """Initialize faces & events database context."""
        if self.ctx.serviceConfig.additionalServicesUsage.lunaEvents:
            EventsDBConfig.initialize(dbType=self.ctx.serviceConfig.eventsDB.type)
            await EventsDBContext.initDBContext(
                dbSettings=self.ctx.serviceConfig.eventsDB,
                storageTime=self.ctx.serviceConfig.storageTime,
            )
            self.ctx.eventsDBAdaptor = EventsDBContext.adaptor
        FacesDBConfig.initialize(dbType=self.ctx.serviceConfig.facesDB.type, databaseNumber=DATABASE_NUMBER)
        await FacesDBContext.initDBContext(
            dbSettings=self.ctx.serviceConfig.facesDB,
            storageTime=self.ctx.serviceConfig.storageTime,
        )
        self.ctx.facesDBAdaptor = FacesDBContext.adaptor 
[docs]    @staticmethod
    async def checkConnectionToAttributesDB(dbSettings: RedisStorageSetting) -> bool:
        """
        Check connections to temporary attributes db
        Args:
            dbSettings: redis settings
        Returns:
            True, if check was passed successfully, else False.
        """
        if not dbSettings.sentinel.sentinels:
            return await checkConnectionToRedis(
                redisHost=dbSettings.host,
                redisPort=dbSettings.port,
                redisPassword=dbSettings.password,
                asyncCheck=True,
            )
        else:
            return await checkConnectionToSentinel(
                sentinels=dbSettings.sentinel.sentinels,
                redisPassword=dbSettings.password,
                masterName=dbSettings.sentinel.masterName,
                asyncCheck=True,
            ) 
[docs]    @staticmethod
    async def checkConnectionToFacesDB(dbSettings: DBSetting) -> bool:
        """
        Check connections to faces db
        Args:
            dbSettings: database settings
        Returns:
            True, if check was passed successfully, else False.
        """
        return await checkConnectionToDB(
            dbSetting=dbSettings,
            checkMigrationVersion=False,
            modelNames=[table.__tablename__ for table in FacesBase.__subclasses__()],
            postfix="luna-faces",
            asyncCheck=True,
        ) 
[docs]    @staticmethod
    async def checkConnectionToEventsDB(dbSettings: DBSetting) -> bool:
        """
        Check connections to events db
        Args:
            dbSettings: database settings
        Returns:
            True, if check was passed successfully, else False.
        """
        return await checkConnectionToDB(
            dbSetting=dbSettings,
            checkMigrationVersion=False,
            modelNames=[table.__tablename__ for table in EventsBase.__subclasses__()],
            postfix="luna-events",
            asyncCheck=True,
        ) 
[docs]    async def shutdown(self):
        """Shutdown application services."""
        if self.ctx.facesDBAdaptor:
            await self.ctx.facesDBAdaptor.close()
        if self.ctx.eventsDBAdaptor:
            await self.ctx.eventsDBAdaptor.close()
        if self.ctx.redisClient:
            self.ctx.redisClient.close()
            await self.ctx.redisClient.wait_closed()
        await super().shutdown()