import logging
import pytz

from datetime import datetime
from psi_elt import get_logger
from psi_elt.abstract_elt_job import AbstractELTJob
from typeguard import typechecked
from typing import Any, Dict


@typechecked
class AbstractRunner:
    def __init__(
        self,
        jobs: Dict[str, Any],
        logger: logging.Logger = None
    ):
        '''
        Initializes an AbstractRunner.

        This class should be treated as abstract and should be extended.
        Child classes must override the _run function. Optionally, child
        classes may override the parse_event and validate_parsed_event
        functions as well.

        This class includes utilties for getting a capture time and
        initializing an ELT job.
        '''
        assert jobs, 'A jobs dict is required to initialize an AbstractRunner'
        self.__jobs = jobs
        self.__logger = logger or get_logger()

    @property
    def logger(self) -> logging.Logger:
        return self.__logger

    def run(self, event: Dict[str, Any]) -> Dict[str, Any]:
        t0 = datetime.now()
        parsed_event = self.parse_event(event)
        self.validate_parsed_event(parsed_event)
        self.logger.info(f'Running with parsed event: {parsed_event}')
        job = self.get_job(
            job_name=parsed_event.pop('name'),
            capture_time=parsed_event['capture_time'],
            job_init_kwargs=parsed_event.pop('job_init_kwargs')
        )
        is_test = parsed_event.pop('is_test')
        s3_uri = self._run(job, is_test, **parsed_event)
        self.logger.info(f'ELT Job completed in {str(datetime.now() - t0)}')
        return {
            'event': event,
            'parsed_event': parsed_event,
            'job': job.to_dict(),
            'results': {
                's3_uri': s3_uri
            }
        }

    def _run(self, job: AbstractELTJob, **parsed_event):
        raise Exception('AbstractRunner._run should be overridden by the child class')

    def parse_event(self, event: Dict[str, Any]) -> Dict[str, Any]:
        raise Exception('AbstractRunner.parse_event should be overridden by the child class')

    def validate_parsed_event(self, parsed_event: Dict[str, Any]):
        raise Exception('AbstractRunner.validate_parsed_event should be overridden by the child class')

    def get_capture_time(self, event: dict) -> datetime:
        if 'capture_time' in event and event['capture_time']:
            # fromisoformat returns a naive datetime object that IS NOT adjusted for the local timezone
            return datetime.fromisoformat(event['capture_time']).replace(tzinfo=pytz.utc)
        elif isinstance(event.get('s3_source'), dict) and event['s3_source'].get('s3_uri'):
            capture_time_ms = event['s3_source']['s3_uri'].split('/')[-1]
            # fromtimestamp returns a naive datetime object that IS adjusted for the local timezone
            return datetime.fromtimestamp(int(capture_time_ms)/1000).astimezone(pytz.utc)
        elif 's3_uri' in event and event['s3_uri']:
            capture_time_ms = event['s3_uri'].split('/')[-1]
            # fromtimestamp returns a naive datetime object that IS adjusted for the local timezone
            return datetime.fromtimestamp(int(capture_time_ms)/1000).astimezone(pytz.utc)
        else:
            # now returns a naive datetime object that IS adjusted for the local timezone
            return datetime.now().astimezone(pytz.utc)

    def get_job(
        self,
        job_name: str,
        capture_time: datetime,
        job_init_kwargs: dict
    ) -> AbstractELTJob:
        assert capture_time, 'A capture_time is required to initialize an ELT job'
        cls = self.__jobs.get(job_name)
        if cls:
            return cls(
                capture_time,
                **job_init_kwargs
            )
        raise Exception(f'Unrecognized ELT job with name: {job_name}')
