from flask_scrapinghub.flag import Color, Execution
from flask_scrapinghub.spider_base import SpiderBase
from scrapinghub import ScrapinghubClient
from scrapinghub.client.exceptions import DuplicateJobError
import logging
import json

logger = logging.getLogger(__name__)


class FlaskScrapinghub(object):

    def __init__(self, app=None, config_prefix="SCRAPINGHUB", spiders_mode=False, spiders_tag='none', **kwargs):
        # 爬蟲模式
        self.spiders_mode = spiders_mode
        self.spiders_tag = spiders_tag
        self.spiders_mode_index = -1
        self.spiders = None
        # 基本配置
        self.client = None
        self.setup = None
        self.provider_kwargs = kwargs
        self.config_prefix = config_prefix
        self.spider_signals = {}
        self.initialized = False
        if app is not None:
            self.init_app(app)

    def init_app(self, app, **kwargs):
        if self.spiders_mode:
            self.spiders = []
            self.spiders_mode_index = 0
        logger.info(f"Spider Mode: {self.spiders_mode}")
        # 讀取 setup_development.json (預設命名 SCRAPINGHUB_SETUP_FILE)
        setup_file = app.config.get(f"{self.config_prefix}_SETUP_FILE", 'setup.json')
        # 取得 api-key，以創建client
        with open(setup_file, 'r') as json_file:
            self.setup = json.load(json_file)
        apikey = self.setup.get('api-key', None)
        if not apikey:
            raise SyntaxError()
        self.client = ScrapinghubClient(apikey)

        # app 讀入 client
        if not hasattr(app, "extensions"):
            app.extensions = {}
        if app.extensions.get(self.config_prefix.lower(), None):
            logger.warning(
                f"Another FlaskScrapinghub instance using the same prefix: \"{self.config_prefix.lower()}\"! ")
        app.extensions[self.config_prefix.lower()] = self
        self.provider_kwargs.update(kwargs)
        # 初始化 spider_signals
        self.init_spider_signals()
        self.initialized = True

    def make_sure_initialized(self):
        if not self.initialized:
            raise Exception('Has not initialized Error')

    def __getattr__(self, name):
        return getattr(self.client, name)

    def init_spider_signals(self):
        projects = self.setup.get("projects", None)
        if not projects or len(projects) == 0:
            self.spider_signals = {}
            return
        for project in projects:
            project_instance = self.client.get_project(project)
            self.spider_signals[project] = {}
            self.spider_signals[project]["jobs"] = project_instance.jobs
            self.spider_signals[project]["spiders"] = []
            for spider in project_instance.spiders.list():
                self.spider_signals[project]["spiders"].append({
                    "name": spider['id'],
                    "status": self.tag_stand_by(),
                    "jobs": project_instance.jobs,
                    "items": None,
                    "logs": None,
                    "job": None
                })

    def check_and_get_spider(self, project_id, spider_id):
        self.make_sure_initialized()
        project = self.spider_signals.get(project_id, None)
        # -----若 project_id不存在-----
        if not project:
            return None
        spiders = project.get("spiders", None)
        # -----若 project_id不存在-----
        if not spiders or len(spiders) == 0:
            return None
        # -----若 project中 spider不存在-----
        if not any(spider.get("name", None) == spider_id for spider in spiders):
            return None
        specific_spider = None
        for spider in self.spider_signals[project_id]['spiders']:
            if spider["name"] == spider_id:
                specific_spider = spider
        return specific_spider

    def view_spider_info(self, project_id, spider_id):
        self.make_sure_initialized()
        spider = self.check_and_get_spider(project_id, spider_id)
        # -----所指定的 spider不存在-----
        if not spider:
            return None
        name = spider.get('name', None)
        status = spider.get('status', None)
        jobs = spider.get('jobs', None)
        job = spider.get('job', None)
        job_status = self.get_job_status(jobs, job) if job and jobs else None
        info = {
            'name': name,
            'spider_status': status,
            'job': job,
            'job_status': job_status
        }
        return info

    def check_spider(self, project_id, spider_id):
        self.make_sure_initialized()
        spider = self.check_and_get_spider(project_id, spider_id)
        # -----所指定的 spider不存在-----
        if not spider:
            return None
        # -----若 spider已完成-----
        if spider['status'] == self.tag_finished():
            return Color.Red
        # -----若 spider待命中-----
        if spider['status'] == self.tag_stand_by():
            return Color.GREEN
        # -----若 spider進行中-----
        if spider['status'] == self.tag_running():
            jobs = spider.get('jobs', None)
            job = spider.get('job', None)
            if not job or not jobs:
                raise Exception('spider停止，但無執行中工作，程式邏輯有誤')
            spider_status = self.get_job_status(jobs, job)
            # 若已結束工作
            if spider_status == "finished":
                spider['status'] = self.tag_finished()
                return Color.Red
            return Color.YELLOW
        # -----若 spider停止中-----
        if spider['status'] == self.tag_stop():
            jobs = spider.get('jobs', None)
            job = spider.get('job', None)
            if not job or not jobs:
                raise Exception('spider停止，但無最後工作，程式邏輯有誤')
            spider_status = self.get_job_status(jobs, job)
            # 若最後工作仍在運行中
            if spider_status == "running":
                return Color.BLACK
            # 若最後工作已處於非運行狀態
            else:
                spider['status'] = self.tag_stand_by()
                spider['items'] = None
                spider['logs'] = None
                spider['job'] = None
                return Color.GREEN

    def run_spider(self, project_id, spider_id, units=1, priority=4, tags=None):

        self.make_sure_initialized()
        spider = self.check_and_get_spider(project_id, spider_id)
        # -----所指定的 spider不存在-----
        if not spider:
            return Execution.NO_SPIDER
        # -----若 spider已完成-----
        if spider['status'] == self.tag_finished():
            logger.warning(f"[{project_id}/{spider_id}] 已完成，需先進行資料轉移始可重新啟動")
            return Execution.FAIL
        # -----若 spider進行中-----
        if spider['status'] == self.tag_running():
            logger.warning(f"[{project_id}/{spider_id}] 進行中，無法啟動，請先停止爬蟲")
            return Execution.FAIL
        # -----若 spider停止中-----
        if spider['status'] == self.tag_stop():
            logger.warning(f"[{project_id}/{spider_id}] 停止中，再次確認當前狀態")
            light = self.check_spider(project_id, spider_id)
            if light != Color.GREEN:
                return Execution.FAIL
            else:
                logger.info(f"[{project_id}/{spider_id}] 已恢復，將自動嘗試啟動爬蟲")
                return self.run_spider(project_id, spider_id, units, priority, tags)

        # -----若 spider待命中-----
        if spider['status'] == self.tag_stand_by():
            logger.info(f"[{project_id}/{spider_id}] 待命中，即將啟動，開始爬蟲作業")

        try:
            job = self.client.get_project(project_id) \
                .jobs.run(spider=spider_id,
                          units=units,
                          priority=priority,
                          tags=tags)
        except DuplicateJobError:
            logger.warning(f"[{project_id}/{spider_id}] 相同爬蟲正在工作中，當前操作無效")
            spider['status'] = self.tag_running()
            running_summary = [i.get('summary') for i in spider['jobs'].summary() if i.get('name') == 'running'][0]
            if running_summary:
                found_spider_status = [s for s in running_summary if s.get('spider') == spider_id]
            else:
                found_spider_status = None
            if not found_spider_status:
                logger.warning(f"[{project_id}/{spider_id}] 當前無無任何爬蟲工作中記錄，邏輯有誤")
            elif len(found_spider_status) == 0:
                logger.warning(f"[{project_id}/{spider_id}] 查無此爬蟲並未工作中，邏輯有誤")
            else:
                spider_status = found_spider_status[0]
                key = spider_status.get('key')
                job = spider['jobs'].get(key)
                spider['job'] = key
                spider['items'] = job.items
                spider['logs'] = job.logs
                if key == f"{project_id}/spider['job']":
                    logger.info(f"[{project_id}/{spider_id}] 爬蟲存在相關工作紀錄，原任務繼續進行")
                else:
                    logger.warning(f"[{project_id}/{spider_id}] 爬蟲無相關紀錄，將立即取消該工作")
                    self.stop_spider(project_id, spider_id)
            return Execution.FAIL

        if spider["name"] == spider_id:
            spider['status'] = self.tag_running()
            spider['items'] = job.items
            spider['logs'] = job.logs
            spider['job'] = job.key
        return Execution.SUCCESS

    def stop_spider(self, project_id, spider_id):

        self.make_sure_initialized()
        spider = self.check_and_get_spider(project_id, spider_id)
        # -----所指定的 spider不存在-----
        if not spider:
            return Execution.NO_SPIDER
        # -----若 spider已完成-----
        if spider['status'] == self.tag_finished():
            logger.warning(f"[{project_id}/{spider_id}] 已完成，無須中斷爬蟲")
            return Execution.FAIL
        # -----若 spider待命中-----
        if spider['status'] == self.tag_stand_by():
            logger.warning(f"[{project_id}/{spider_id}] 待命中，無須中斷爬蟲")
            return Execution.FAIL
        # -----若 spider停止中-----
        if spider['status'] == self.tag_stop():
            logger.warning(f"[{project_id}/{spider_id}] 停止中，無須中斷爬蟲")
            return Execution.FAIL
        # -----若 spider進行中-----
        if spider['status'] == self.tag_running():
            logger.info(f"[{project_id}/{spider_id}] 進行中，即將中斷爬蟲")

        spider['jobs'].get(spider['job']).cancel()
        spider['status'] = self.tag_stop()
        spider['items'] = None
        spider['logs'] = None

        return Execution.SUCCESS

    def resume_spider(self, project_id, spider_id):

        self.make_sure_initialized()
        spider = self.check_and_get_spider(project_id, spider_id)
        # -----所指定的 spider不存在-----
        if not spider:
            return Execution.NO_SPIDER
        # -----若 spider待命中-----
        if spider['status'] == self.tag_stand_by():
            logger.warning(f"[{project_id}/{spider_id}] 待命中，無法恢復待命狀態")
            return Execution.FAIL
        # -----若 spider停止中-----
        if spider['status'] == self.tag_stop():
            logger.warning(f"[{project_id}/{spider_id}] 停止中，無法恢復待命狀態")
            return Execution.FAIL
        # -----若 spider進行中-----
        if spider['status'] == self.tag_running():
            logger.warning(f"[{project_id}/{spider_id}] 進行中，無法恢復待命狀態")
            return Execution.FAIL
        # -----若 spider已完成-----
        if spider['status'] == self.tag_finished():
            logger.info(f"[{project_id}/{spider_id}] 已完成，改為待命中狀態，並銷毀暫存資料實體")
            if spider["name"] == spider_id:
                spider['status'] = self.tag_stand_by()
                spider['items'] = None
                spider['logs'] = None
            return Execution.SUCCESS

    def retrieve_item(self, project_id, spider_id):

        self.make_sure_initialized()
        spider = self.check_and_get_spider(project_id, spider_id)
        # -----所指定的 spider不存在-----
        if not spider:
            return None
        # -----若 spider待命中-----
        if spider['status'] == self.tag_stand_by():
            logger.warning(f"[{project_id}/{spider_id}] 待命中，不存在可供搬運資料")
            return None
        # -----若 spider停止中-----
        if spider['status'] == self.tag_stop():
            logger.warning(f"[{project_id}/{spider_id}] 停止中，不存在可供搬運資料")
            return None
        # -----若 spider進行中-----
        if spider['status'] == self.tag_running():
            logger.warning(f"[{project_id}/{spider_id}] 進行中，尚不提供可供搬運資料")
            return None
        # -----若 spider已完成-----
        if spider['status'] == self.tag_finished():
            logger.info(f"[{project_id}/{spider_id}] 已完成，將資料實體轉移至應用程式使用")
            job = spider.get('job', None)
            jobs = spider.get('jobs', None)
            if not job or not jobs:
                raise Exception("爬蟲已完成，無對應工作，程式邏輯異常")
            items = jobs.get(job).items
            if not items:
                raise Exception("爬蟲已完成，無可供操作實體，程式邏輯異常")
            return items

    # 以下為 spider mode 的功能
    def add_spider(self, add_spider):

        self.make_sure_initialized()
        self.make_sure_spider_mode()

        # 若傳入非 spider base物件則報錯
        if not isinstance(add_spider, SpiderBase):
            raise Exception("傳入非 spider_base 之子物件")
        if not self.check_and_get_spider(add_spider.project_id, add_spider.spider_id):
            raise Exception("傳入 spider 非紀錄之spider")

        if not add_spider.tag:
            add_spider.add_tag(self.spiders_tag)
        # 若 add 相同 spider id 和 project id 的 spider，刪除並從後方插入
        index = 0
        for spider in self.spiders:
            if spider.project_id == add_spider.project_id and spider.spider_id == add_spider.spider_id:
                del self.spiders[index]
            else:
                index += 1
        self.spiders.append(add_spider)
        logger.info(
            f"[Spider Mode]: 已添加 {add_spider.project_id}/{add_spider.spider_id}, 序列:{len(self.spiders)}, 標籤: {add_spider.tag}")

    def wake_spider(self):
        self.make_sure_initialized()
        self.make_sure_spider_mode()

        spider = self.spiders[self.spiders_mode_index]

        light = self.check_spider(spider.project_id, spider.spider_id)

        next_round = 0 if self.spiders_mode_index + 1 >= len(self.spiders) else self.spiders_mode_index + 1

        self.spiders_mode_index = next_round

        spider.react(light, next_round, self)

    def start_spider(self):
        self.make_sure_initialized()
        self.make_sure_spider_mode()
        # 並未加入爬蟲
        if len(self.spiders) == 0:
            logger.warning("Didn't add any spider into spiders. Cancel starting!")
            return
        logger.debug("try to start spider mode scraping")
        # 將當前爬蟲模式的次序歸零
        # new
        self.spiders_mode_index = 0

        leader_spider = self.spiders[self.spiders_mode_index]

        if leader_spider.shall_start(self):
            logger.debug("trying success. Spider mode scraping started")
            self.wake_spider()

        else:
            logger.debug("trying fail. Spider mode scraping cannot pass 'shall_start()' restriction.")
        # old (v0.0.1)
        # index = self.spiders_mode_index + 1
        # if index >= len(self.spiders):
        #     index = 0
        # spider = self.spiders[index]
        # if spider.shall_start(self):
        #     self.wake_spider()

    def set_spiders_mode_index(self, order):

        self.make_sure_initialized()
        self.make_sure_spider_mode()

        if 0 <= order <= len(self.spiders) - 1:
            self.spiders_mode_index = order
            return True
        raise Exception("Setting index is out of spiders range")

    def check_spiders_status(self):

        self.make_sure_initialized()
        self.make_sure_spider_mode()

        spiders_status = {}
        index = 0
        for spider_item in self.spiders:
            spider = self.check_and_get_spider(spider_item.project_id, spider_item.spider_id)
            if not spider:
                raise Exception("自動爬蟲模式出現未被紀錄之spider，程式邏輯異常")
            spiders_status[f"{spider_item.project_id}/{spider_item.spider_id}"] = {
                "name": spider.get('name', None),
                "index": index,
                "status": spider.get('status', None)
            }
            index += 1
        return spiders_status

    def check_spiders(self):

        self.make_sure_initialized()
        self.make_sure_spider_mode()

        for spider in self.spiders:
            if not self.check_spider(spider.project_id, spider.spider_id):
                raise Exception("存在未被記錄之spider，程式邏輯異常")

    def make_sure_spider_mode(self):
        if not self.spiders_mode:
            raise Exception("Not turn on spider mode.")

    @staticmethod
    def get_job_status(jobs, job):
        return jobs.get(job).metadata.get('state')

    @classmethod
    def tag_stand_by(cls):
        return "stand by"

    @classmethod
    def tag_running(cls):
        return "running"

    @classmethod
    def tag_finished(cls):
        return "finished"

    @classmethod
    def tag_stop(cls):
        return "stop"


