import json
import os
import threading
from concurrent.futures import ThreadPoolExecutor
from datetime import (
    date,
)
from threading import current_thread
from typing import (
    List,
    Optional,
)

from order_picture_base_model import (
    OrderState,
)
from ._Boto3S3StorageService import Boto3S3StorageService
from ._OrderPictureFile import OrderPictureFile


class ExportedOrderPictureService(object):
    def __init__(
        self,
        storage_service: Boto3S3StorageService,
        context_name: str,
        app_name: str,
    ):
        self.storage_service = storage_service
        self.context_name = context_name

        self.base_processed_dir = f"{self.context_name}/{app_name}/processed/order_picture"

        cpu_count = os.cpu_count()
        if cpu_count is None:
            cpu_count = 1

        current_context = getattr(threading.current_thread(), "_edp_context_name", None)

        def _context_name_initializer():
            if current_context is not None:
                setattr(threading.current_thread(), "_edp_context_name", current_context)

        self.thread_pool = ThreadPoolExecutor(
            max_workers=20 * cpu_count,
            thread_name_prefix=f"{current_thread().name}$ExportedOrderPictureServiceThread",
            initializer=_context_name_initializer,
        )

    def get_files_of_store_and_day(
        self,
        store_code: str,
        business_date: date,
    ) -> List[OrderPictureFile]:

        business_date_dir = f"{business_date.year}/{business_date.month}/{business_date.day}"
        new_sales_path = f"{self.context_name}/order_picture/{store_code}/{business_date_dir}/"
        processed_sales_path = f"{self.base_processed_dir}/{store_code}/{business_date_dir}/"

        new_sales_result = self.thread_pool.submit(
            self.storage_service.list_files,
            new_sales_path, lambda x: True
        )
        processed_sales_result = self.thread_pool.submit(
            self.storage_service.list_files,
            processed_sales_path, lambda x: True
        )

        new_sales = new_sales_result.result()
        already_processed = processed_sales_result.result()

        file_names = sorted(already_processed + new_sales)

        ret = []
        for file_name, content in self.thread_pool.map(
            lambda x: (x, self.storage_service.get_file_content(x)),
            file_names
        ):
            record = json.loads(content.decode())
            ret.append(OrderPictureFile(full_path=file_name, record=record))

        return ret

    def get_first_of_store(self, store_code) -> Optional[date]:
        path = f"{self.context_name}/order_picture/{store_code}/"
        available_years = self.storage_service.list_directories(path=path)
        if len(available_years) == 0:
            return None

        min_year = min([int(year) for year in available_years])

        path = f"{path}{min_year}/"
        available_months = self.storage_service.list_directories(path=path)
        min_month = min([int(month) for month in available_months])

        path = f"{path}{min_month}/"
        available_days = self.storage_service.list_directories(path=path)
        min_day = min([int(day) for day in available_days])

        return date(
            year=min_year,
            month=min_month,
            day=min_day,
        )

    def move_to_processed(self, order_picture_files: List[OrderPictureFile]) -> None:
        list(self.thread_pool.map(self._move_order, order_picture_files))

    def close(self):
        self.thread_pool.shutdown(
            wait=True,
            cancel_futures=True,
        )
        self.thread_pool = None

    def _move_order(self, order_picture_file: OrderPictureFile):
        if order_picture_file.order_picture.state != OrderState.Paid:
            self.storage_service.delete_file(order_picture_file.full_path)
            return

        if order_picture_file.full_path.startswith(self.base_processed_dir):
            return

        business_date = order_picture_file.business_date
        store_code = order_picture_file.store_code
        business_date_dir = f"{business_date.year}/{business_date.month}/{business_date.day}"
        processed_dir = f"{self.base_processed_dir}/{store_code}/{business_date_dir}"
        self.storage_service.copy_file(
            source_path=order_picture_file.full_path,
            destination_path=f"{processed_dir}/{order_picture_file.file_name}"
        )
        self.storage_service.delete_file(order_picture_file.full_path)
