from configparser import ConfigParser
from datetime import date
from io import TextIOBase
import logging
from logging import FileHandler, StreamHandler
import os
import sys
from time import sleep
from typing import Any, Callable, List, Optional, TextIO

from requests.cookies import create_cookie

from tator.openapi.tator_openapi import Media, Section, TatorApi
from tator.util import upload_attachment
from tqdm import tqdm

logger = logging.getLogger(__name__)

SAIL_DATE_PLACEHOLDER = "NO_SAIL_DATE"
LAND_DATE_PLACEHOLDER = "NO_LAND_DATE"
VESSEL_NAME_MAP = {
    "1022773": "Eagle Eye II",
    "1027010": "Alex Marie",
    "1029712": "Kenny Boy",
    "1031845": "Destiny",
    "1038712": "Ellen Jean",
    "1049623": "Leo B",
    "1061277": "Miss Kaleigh",
    "1075516": "Jamie B",
    "1081640": "Capt Easy",
    "1097631": "Hanna Katherine",
    "1231179": "Full Timer",
    "1231553": "Dayboat One",
    "1275363": "Market Price",
    "507646": "Daytona",
    "510728": "Miss Suzanne",
    "539456": "Bookie",
    "541694": "Eaglet II",
    "557860": "Independence",
    "579208": "Galilean",
    "591301": "Charleston Star",
    "592269": "Capt. Gorman III",
    "595941": "Blue Wave",
    "596805": "Mary Ann",
    "597797": "Bluefin",
    "600048": "Kim Thanh II",
    "600293": "4 Cs",
    "600322": "Bobalou",
    "604915": "Frances Ann",
    "608436": "Christopher Joe",
    "609121": "Carol Ann",
    "610006": "Captain Michael",
    "610080": "Blessings",
    "611243": "Erica Lynn",
    "616696": "Cynthia Renee",
    "620696": "Albi",
    "622027": "Carter Anthony",
    "624406": "Dayboat Too",
    "628300": "Bigeye",
    "630306": "Defiance",
    "633432": "Kelly Ann",
    "638863": "Miss Shannon",
    "641486": "Miss Ann",
    "646506": "Janice Ann",
    "651698": "Intrepido II",
    "652211": "Lisa Ann",
    "655108": "Miss Haylee II",
    "656259": "Kristin Lee",
    "659026": "Eyelander",
    "661098": "Miss Rita",
    "661284": "Hannah Boden",
    "662409": "Fine Tuna",
    "665962": "Linnea C",
    "668316": "Capt. Luu",
    "669872": "Eagle Eye",
    "671174": "Delphinus",
    "673556": "Candace",
    "674417": "Bear",
    "680845": "Endeavor",
    "683311": "Iron Maiden",
    "904300": "Kim Nhi",
    "905679": "Capt. Mike",
    "908045": "Julianne",
    "911431": "Capt. David",
    "912738": "Joshua Nicole",
    "913385": "2nd Wind",
    "918933": "Miss Lena",
    "919799": "Blue Sea I",
    "929813": "Capt. Bob",
    "936041": "Cecelia II",
    "937416": "Kim Thanh",
    "956008": "Dakota",
    "957958": "Miss Jane",
    "969495": "Hannah Story",
    "983321": "Blue Sea 2",
    "997091": "Alexandria Dawn",
    "FL5295PN": "Big B",
    "MD9128BD": "Integrity",
    "MS1993DD": "Angel's Grace",
    "NC2015WS": "Kaitlyn C",
    "NC3377EM": "Big Dave",
    "NC5126DC": "Hardway",
    "NC5516DF": "Fish Wish",
    "NC6362EX": "Big Skipper",
    "NC8831WR": "Body Count",
    "NJ4755FP": "Backwash",
}


def validate_type_from_config(
    api: TatorApi,
    config: ConfigParser,
    config_field: str,
    project_id: int,
    tator_type: str,
    list_filters=None,
) -> int:
    if tator_type not in ["media_type", "file_type"]:
        raise ValueError(f"Cannot validate {config_field=} for {tator_type=}")

    type_id = config["Tator"].getint(config_field, -1)
    if type_id > 0:
        try:
            getattr(api, f"get_{tator_type}")(type_id)
        except Exception as exc:
            raise ValueError(f"Could not find {config_field} with id {type_id}") from exc
    else:
        try:
            types = getattr(api, f"get_{tator_type}_list")(project_id)
        except Exception as exc:
            raise RuntimeError(f"Could not list {config_field}s from project {project_id}") from exc
        if list_filters:
            for attr, value in list_filters:
                types = [
                    _type
                    for _type in types
                    if hasattr(_type, attr) and getattr(_type, attr) == value
                ]
        if len(types) > 1:
            raise ValueError(
                f"Project {project_id} has more than one {config_field}, specify one of the "
                f"following in the config: {types}"
            )
        type_id = types[0].id
    return type_id


def safe_get_type(type_id: int, type_getter: Callable[[int], Any]):
    type_inst = None
    try:
        type_inst = type_getter(type_id)
    except Exception:
        logger.error("Could not find type %d", type_id, exc_info=True)
    return type_inst


def build_section_name(
    vessel_name: str, sail_date: Optional[date] = None, land_date: Optional[date] = None
) -> str:
    try:
        sail_date_str = sail_date.strftime("%Y-%m-%d") if sail_date else SAIL_DATE_PLACEHOLDER
    except Exception:
        sail_date_str = SAIL_DATE_PLACEHOLDER
    try:
        land_date_str = land_date.strftime("%Y-%m-%d") if land_date else LAND_DATE_PLACEHOLDER
    except Exception:
        land_date_str = LAND_DATE_PLACEHOLDER

    return f"{vessel_name} ({sail_date_str} - {land_date_str})"


def section_from_tus(
    tator_api: TatorApi, project_id: int, tator_user_sections: str
) -> Optional[Section]:
    section = None
    try:
        section_list = tator_api.get_section_list(project_id)
    except Exception:
        pass
    else:
        if isinstance(section_list, list):
            for sec in section_list:
                if sec.tator_user_sections == tator_user_sections:
                    section = sec
                    break
    return section


def section_from_name(tator_api: TatorApi, project_id: int, section_name: str) -> Optional[Section]:
    section = None
    try:
        section_list = tator_api.get_section_list(project_id)
    except Exception:
        pass
    else:
        if isinstance(section_list, list):
            for sec in section_list:
                if sec.name == section_name:
                    section = sec
                    break
    return section


class HmsLogHandler(TextIOBase):
    log_filename = os.path.join(os.getcwd(), f"{__name__.split('.')[0]}.log")

    def __init__(
        self, tator_api: TatorApi, console_log_level: int, log_filename: Optional[str] = None
    ):
        super().__init__()
        self._api: TatorApi = tator_api
        self._log_filename = log_filename or HmsLogHandler.log_filename
        root_logger = logging.getLogger()
        root_logger.setLevel(logging.DEBUG)
        self._console_handler: StreamHandler = logging.StreamHandler()
        self._console_handler.setLevel(console_log_level)
        self._file_handler: FileHandler = FileHandler(filename=self._log_filename, mode="w")
        self._file_handler.setLevel(logging.DEBUG)
        formatter = logging.Formatter("[%(asctime)s - %(levelname)s - %(name)s]: %(message)s")
        self._console_handler.setFormatter(formatter)
        self._file_handler.setFormatter(formatter)
        root_logger.addHandler(self._console_handler)
        root_logger.addHandler(self._file_handler)
        self._stdout: TextIO = sys.stdout
        self._stderr: TextIO = sys.stderr

        # Set the summary image id before leaving the context and HmsLogHandler will automatically
        # upload its log file as an attachment to the image
        self._summary_image_id: Optional[int] = None

    @property
    def summary_image_id(self):
        return self._summary_image_id

    @summary_image_id.setter
    def summary_image_id(self, value):
        if self._summary_image_id is not None:
            raise RuntimeError(f"Summary image id already set to {self._summary_image_id}!")
        if value is not None:
            self._summary_image_id = int(value)

    def write(self, *args, **kwargs):
        self._console_handler.stream.write(*args, **kwargs)
        self._file_handler.stream.write(*args, **kwargs)

    def __enter__(self):
        sys.stdout = self
        sys.stderr = self
        return self

    def __exit__(self, *_):
        # Close file handler and reset sys.stdout and sys.stderr
        self._file_handler.close()
        sys.stdout = self._stdout
        sys.stderr = self._stderr

        # Upload log file to summary image
        if self.summary_image_id:
            try:
                for _ in upload_attachment(self._api, self.summary_image_id, self._log_filename):
                    pass
            except Exception:
                pass


def wait_for_thumbs(tator_api: TatorApi, project_id: int, created_ids: List[int]) -> bool:
    # Wait for imports to generate thumbnails
    logger.info("Waiting for transcodes to generate thumbnails for all uploads...")
    pbar = tqdm(total=len(created_ids), dynamic_ncols=True, position=0, ascii=False)
    created_ids_copy = created_ids.copy()
    wait_time = 15  # seconds
    while created_ids_copy:
        logger.info("Media gif thumbnail creation not complete for %d media", len(created_ids_copy))
        media_list = []
        try:
            media_list = tator_api.get_media_list_by_id(
                project_id, media_id_query={"ids": created_ids_copy}
            )
        except Exception:
            logger.warning("Could not get media list")

        for media in media_list:
            if isinstance(media, Media) and getattr(media.media_files, "thumbnail_gif", None):
                # Remove the id from the list to check
                logger.debug("Media gif thumbnail creation complete for media %d", media.id)
                created_ids_copy.remove(media.id)
                pbar.update()
        if created_ids_copy:
            sleep(wait_time)

    return True
