import argparse
import datetime as dt
import time
from typing import Optional
from sqlalchemy import not_

from sotodlib.site_pipeline.monitor import Monitor
from sotodlib.site_pipeline.util import init_logger

from sotodlib.io.imprinter import (
    Imprinter,
    Books,
    BOUND,
    UNBOUND,
    UPLOADED,
    FAILED,
    DONE,
)

logger = init_logger(__name__, "update_book_plan: ")

def main(
    config: str,
    min_ctime: Optional[float] = None,
    max_ctime: Optional[float] = None,
    stream_ids: Optional[str] = None,
    force_single_stream: bool = False,
    update_delay: float = 1,
    update_delay_timecodes: Optional[float] = 7,
    min_ctime_timecodes: Optional[float] = None,
    max_ctime_timecodes: Optional[float] = None,
    from_scratch: bool = False,
    use_monitor: bool = False,
    ):
    """
    Update the book plan database with new data from the g3tsmurf database.

    Parameters
    ----------
    config : str
        Path to config file for imprinter
    min_ctime : Optional[float], optional
        The minimum ctime to include in the book plan, by default None
    max_ctime : Optional[float], optional
        The maximum ctime to include in the book plan, by default None
    stream_ids : Optional[str], optional
        The stream ids to consider, list supplied as a comma separated string
        (e.g. "1,2,3"), by default None
    force_single_stream : bool, optional
        If True, tream multi-wafer data as if it were single wafer data, by default False
    update_delay : float, optional
        The range of time to search through g3tsmurf db for new data in units of
        days, by default 1
    update_delay_timecodes : float, optional
        The range of time to search through g3tsmurf db for new data in units of
        days for timecode books, by default 7
    min_ctime_timecodes : Optional[float], optional
        The minimum ctime to include in the book plan for timecode (hk, smurf, stray) books, by default None
    max_ctime_timecodes : Optional[float], optional
        The maximum ctime to include in the book planor timecode (hk, smurf, stray) books, by default None
    from_scratch : bool, optional
        If True, start to search from beginning of time, by default False
    use_monitor : bool
        if True, will send monitor information to influx, set to false by
        default so we can use identical config files for development
    """
    if stream_ids is not None:
        stream_ids = stream_ids.split(",")

    imprinter = Imprinter(
        config, 
        db_args={'connect_args': {'check_same_thread': False}},
        logger=logger,
        make_db=from_scratch,
    )
    
    # leaving min_ctime and max_ctime as None will go through all available 
    # data, so preferreably set them to a reasonable range based on update_delay
    if not from_scratch and min_ctime is None:
        min_ctime = dt.datetime.now() - dt.timedelta(days=update_delay)
    if isinstance(min_ctime, dt.datetime):
        min_ctime = min_ctime.timestamp()
    if isinstance(max_ctime, dt.datetime):
        max_ctime = max_ctime.timestamp()

    # obs and oper books
    logger.info("Registering obs/oper Books")
    imprinter.update_bookdb_from_g3tsmurf(
        min_ctime=min_ctime, max_ctime=max_ctime,
        ignore_singles=False,
        stream_ids=stream_ids,
        force_single_stream=force_single_stream
    )

    ## over-ride timecode book making if specific values given
    if update_delay_timecodes is None and min_ctime_timecodes is None:
        min_ctime_timecodes = min_ctime
    elif min_ctime_timecodes is None:
        min_ctime_timecodes = (
            dt.datetime.now() - dt.timedelta(days=update_delay_timecodes)
        )
    if max_ctime_timecodes is None:
        max_ctime_timecodes = max_ctime

    if isinstance(min_ctime_timecodes, dt.datetime):
        min_ctime_timecodes = min_ctime_timecodes.timestamp()
    if isinstance(max_ctime, dt.datetime):
        max_ctime_timecodes = max_ctime_timecodes.timestamp()

    # hk books
    logger.info("Registering any HK Books")
    imprinter.register_hk_books(
        min_ctime=min_ctime_timecodes, 
        max_ctime=max_ctime_timecodes,
    )
    # smurf and stray books
    logger.info("Registering any timecode Books")
    imprinter.register_timecode_books(
        min_ctime=min_ctime_timecodes, 
        max_ctime=max_ctime_timecodes,
    )

    monitor = None
    if use_monitor and "monitor" in imprinter.config:
        logger.info("Will send monitor information to Influx")
        try:
            monitor = Monitor.from_configs(
                imprinter.config["monitor"]["connect_configs"]
            )
        except Exception as e:
            logger.error(f"Monitor connectioned failed {e}")
            monitor = None

    if monitor is not None:
        logger.info("Sending Updates to monitor")
        record_book_counts(monitor, imprinter)
    

def record_book_counts(monitor, imprinter):
    """Send a record of the current book count status to the InfluxDb
    site-pipeline montir
    """
    tags = [{"telescope" : imprinter.config["monitor"]["telescope"]}]
    log_tags = {}
    script_run = time.time()

    session = imprinter.get_session()
    def get_count( q ):
        return session.query(Books).filter(q).count()
    
    monitor.record(
        "unbound", 
        [ get_count(Books.status == UNBOUND) ], 
        [script_run], 
        tags, 
        imprinter.config["monitor"]["measurement"], 
        log_tags=log_tags
    )

    monitor.record(
        "bound", 
        [ get_count(Books.status == BOUND) ], 
        [script_run], 
        tags, 
        imprinter.config["monitor"]["measurement"], 
        log_tags=log_tags
    )

    monitor.record(
        "uploaded", 
        [ get_count(Books.status == UPLOADED) ], 
        [script_run], 
        tags, 
        imprinter.config["monitor"]["measurement"], 
        log_tags=log_tags
    )

    monitor.record(
        "failed", 
        [ get_count(Books.status == FAILED) ], 
        [script_run], 
        tags, 
        imprinter.config["monitor"]["measurement"], 
        log_tags=log_tags
    )

    monitor.record(
        "done", 
        [ get_count(Books.status == DONE) ], 
        [script_run], 
        tags, 
        imprinter.config["monitor"]["measurement"], 
        log_tags=log_tags
    )

    monitor.record(
        "has_level2", 
        [ get_count(not_(Books.lvl2_deleted)) ], 
        [script_run], 
        tags, 
        imprinter.config["monitor"]["measurement"], 
        log_tags=log_tags
    )

    monitor.write()

def get_parser(parser=None):
    if parser is None:
        parser = argparse.ArgumentParser()
    parser.add_argument('--config', type=str, help="g3tsmurf db configuration file")
    parser.add_argument('--min-ctime', type=float, help="Minimum creation time")
    parser.add_argument('--max-ctime', type=float, help="Maximum creation time")
    parser.add_argument('--stream-ids', type=str, help="Stream IDs")
    parser.add_argument(
        '--force-single-stream', help="Force single stream", action="store_true"
    )
    parser.add_argument(
        '--update-delay', type=float, 
        help="Days to subtract from now to set as minimum ctime",
        default=1
    )
    parser.add_argument(
        '--from-scratch', help="Builds or updates database from scratch",
        action="store_true"
    )
    parser.add_argument(
        '--min-ctime-timecodes', type=float, 
        help="Minimum creation time for timecode books"
    )
    parser.add_argument(
        '--max-ctime-timecodes', type=float, 
        help="Maximum creation time for timecode books"
    )
    parser.add_argument(
        '--update-delay-timecodes', type=float, 
        help= "Days to subtract from now to set as minimum ctime "
              "for timecode books",
        default=7
    )
    parser.add_argument('--use-monitor', help="Send updates to influx",
                        action="store_true")
    return parser


if __name__ == "__main__":
    parser = get_parser(parser=None)
    args = parser.parse_args()
    main(**vars(args))