import praw
import inspect, os, sys # set the BASE_DIR
import simplejson as json
import datetime, yaml, time, csv
import reddit.connection
import reddit.praw_utils as praw_utils
import reddit.queries
import sqlalchemy
from dateutil import parser
from utils.common import *
from app.models import Base, SubredditPage, Subreddit, User, Post, ModAction, PrawKey, Comment
from app.models import Experiment, ExperimentThing, ExperimentAction, ExperimentThingSnapshot
from app.models import EventHook
from sqlalchemy import and_, or_

### LOAD ENVIRONMENT VARIABLES
BASE_DIR = os.path.join(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))), "..","..")
ENV = os.environ['CS_ENV']


class ExperimentConfigurationError(Exception):
    def __init__(self, message, errors = []):
        # Call the base class constructor with the parameters it needs
        super().__init__(message)
        # Now for your custom code...
        #self.errors = errors


class ExperimentController():
    def __init__(self, experiment_name, db_session, r, log, required_keys):
        self.db_session = db_session
        self.log = log
        self.r = r
        self.required_keys = required_keys
        self.load_experiment_config(required_keys, experiment_name)
        self.log.info("Initializing experiment {0}".format(experiment_name))

    def get_experiment_config(self, required_keys, experiment_name): 
        experiment_file_path = os.path.join(BASE_DIR, "config", "experiments", experiment_name) + ".yml"
        with open(experiment_file_path, 'r') as f:
            try:
                experiment_config_all = yaml.full_load(f)
            except yaml.YAMLError as exc:
                self.log.error("{0}: Failure loading experiment yaml {1}".format(
                    self.__class__.__name__, experiment_file_path), str(exc))
                sys.exit(1)
        if(ENV not in experiment_config_all.keys()):
            self.log.error("{0}: Cannot find experiment settings for {1} in {2}".format(
                self.__class__.__name__, ENV, experiment_file_path))
            sys.exit(1)

        experiment_config = experiment_config_all[ENV]
        for key in required_keys:
            if key not in experiment_config.keys():
                self.log.error("{0}: Value missing from {1}: {2}".format(
                    self.__class__.__name__, experiment_file_path, key))
                sys.exit(1)
        return experiment_config

    def load_experiment_config(self, required_keys, experiment_name):
        experiment_config = self.get_experiment_config(required_keys, experiment_name)
        experiment = self.db_session.query(Experiment).filter(Experiment.name == experiment_name).first()
        if(experiment is None):

            condition_keys = []

            ## LOAD RANDOMIZED CONDITIONS (see CivilServant-Analysis)
            for condition in experiment_config['conditions'].values():
                with open(os.path.join(BASE_DIR, "config", "experiments", condition['randomizations']), "r") as f:
                    reader = csv.DictReader(f)
                    randomizations = []
                    for row in reader:
                        randomizations.append(row)
                        condition['randomizations']  = randomizations

            experiment = Experiment(
                name = experiment_name,
                controller = self.__class__.__name__,
                start_time = parser.parse(experiment_config['start_time']),
                end_time = parser.parse(experiment_config['end_time']),
                settings_json = json.dumps(experiment_config)
            )
            self.db_session.add(experiment)
            self.db_session.commit()
        
        ### SET UP INSTANCE PROPERTIES
        self.experiment = experiment
        self.experiment_settings = json.loads(self.experiment.settings_json)
        

        self.experiment_name = experiment_name
        self.dry_run = experiment_config.get("dry_run", False)

        for key in ['subreddit', 'subreddit_id', 'shadow_subreddit', 'shadow_subreddit_id', 
                    'username', 'max_eligibility_age', 'min_eligibility_age']:
            if key in required_keys:
                setattr(self, key, experiment_config[key])

        # LOAD EVENT HOOKS
        if 'event_hooks' in required_keys:
            self.load_event_hooks(experiment_config)

    def load_event_hooks(self, experiment_config):
        hooks = experiment_config['event_hooks']

        now = datetime.datetime.utcnow()
        for hook_name in hooks:
            hook = self.db_session.query(EventHook).filter(
                EventHook.name == hook_name).first()
            if not hook:
                call_when_str = hooks[hook_name]['call_when']
                if call_when_str == "EventWhen.BEFORE":
                    call_when = EventWhen.BEFORE.value
                elif call_when_str == "EventWhen.AFTER":
                    call_when = EventWhen.AFTER.value
                else:
                    self.log.error("{0}: While loading event hooks, call_when string incorrectly formatted: {1}".format(
                        self.__class__.__name__, call_when_str))
                    sys.exit(1)

                hook_record = EventHook(
                    name = hook_name,
                    created_at = now,
                    experiment_id = self.experiment.id,
                    is_active = hooks[hook_name]['is_active'],
                    call_when = call_when,
                    caller_controller = hooks[hook_name]['caller_controller'],
                    caller_method = hooks[hook_name]['caller_method'],
                    callee_module = hooks[hook_name]['callee_module'],
                    callee_controller = hooks[hook_name]['callee_controller'],
                    callee_method = hooks[hook_name]['callee_method'])
                self.db_session.add(hook_record)
                self.db_session.commit()

    ###########################

    
    def identify_condition(self, obj):
        for label in self.experiment_settings['conditions'].keys():
            detection_method = getattr(self, "identify_"+label)
            if(detection_method(obj)):
                return label
        return None