import datetime
from typing import Optional

from folkugat_web.dal.sql import Connection, get_connection
from folkugat_web.model import sessions as model
from folkugat_web.model.sql import OrderCol, Range
from folkugat_web.typing import OptionalListOrValue as OLV


def create_db(con: Optional[Connection] = None):
    with get_connection(con) as con:
        create_sessions_table(con)


def drop_sessions_table(con: Connection):
    query = "DROP TABLE IF EXISTS sessions"
    cur = con.cursor()
    cur.execute(query)


def create_sessions_table(con: Connection):
    query = """
      CREATE TABLE IF NOT EXISTS sessions (
        id INTEGER PRIMARY KEY,
        date TEXT NOT NULL,
        start_time TEXT NOT NULL,
        end_time TEXT NOT NULL,
        venue_name TEXT,
        venue_url TEXT,
        is_live BOOLEAN DEFAULT false
    )
    """
    cur = con.cursor()
    cur.execute(query)


def _session_to_row(sessio: model.Session) -> dict:
    return {
        'id': sessio.id,
        'date': sessio.date,
        'start_time': sessio.start_time.isoformat(),
        'end_time': sessio.end_time.isoformat(),
        'venue_name': sessio.venue.name,
        'venue_url': sessio.venue.url,
        'is_live': sessio.is_live,
    }


def _row_to_session(row: tuple) -> model.Session:
    return model.Session(
        id=row[0],
        date=datetime.date.fromisoformat(row[1]),
        start_time=datetime.time.fromisoformat(row[2]),
        end_time=datetime.time.fromisoformat(row[3]),
        venue=model.SessionVenue(
            name=row[4],
            url=row[5],
        ),
        is_live=row[6],
    )


def insert_session(session: model.Session, con: Optional[Connection] = None):
    query = """
      INSERT INTO sessions
        (id, date, start_time, end_time, venue_name, venue_url, is_live)
      VALUES
        (:id, :date, :start_time, :end_time, :venue_name, :venue_url, :is_live)
      RETURNING *
    """
    data = _session_to_row(session)
    with get_connection(con) as con:
        cur = con.cursor()
        cur.execute(query, data)
        row = cur.fetchone()
        return _row_to_session(row)


def update_session(session: model.Session, con: Optional[Connection] = None):
    query = """
      UPDATE sessions SET
        date = :date, start_time = :start_time, end_time = :end_time,
        venue_name = :venue_name, venue_url = :venue_url, is_live = :is_live
      WHERE id = :id
    """
    data = _session_to_row(session)
    with get_connection(con) as con:
        cur = con.cursor()
        cur.execute(query, data)


def _filter_clause(session_id: Optional[int] = None,
                   date_range: Optional[Range[datetime.date]] = None,
                   is_live: Optional[bool] = None) -> tuple[str, dict]:
    filter_clauses = []
    filter_data = {}

    if session_id is not None:
        filter_clauses.append(f"id = :session_id")
        filter_data["session_id"] = session_id

    if date_range:
        if ub := date_range.upper_bound():
            operator = "<=" if ub[1] else "<"
            filter_clauses.append(f"date {operator} :date_ub")
            filter_data["date_ub"] = ub[0]
        if lb := date_range.lower_bound():
            operator = ">=" if lb[1] else ">"
            filter_clauses.append(f"date {operator} :date_lb")
            filter_data["date_lb"] = lb[0]

    if is_live is not None:
        filter_clauses.append(f"is_live = :is_live")
        filter_data["is_live"] = is_live

    if filter_clauses:
        filter_clause_str = " AND ".join(filter_clauses)
        filter_clause = f"WHERE {filter_clause_str}"
        return filter_clause, filter_data
    else:
        return "", {}


def _order_clause(order_by: OLV[OrderCol[model.SessionCols]]) -> str:
    if not order_by:
        return ""
    if not isinstance(order_by, list):
        order_by = [order_by]

    order_clauses = [f"{ocol.column.value} {ocol.order.value}" for ocol in order_by]
    order_clauses_str = " ".join(order_clauses)
    return f"ORDER BY {order_clauses_str}"


def get_sessions(session_id: Optional[int] = None,
                 date_range: Optional[Range[datetime.date]] = None,
                 is_live: Optional[bool] = None,
                 order_by: OLV[OrderCol[model.SessionCols]] = None,
                 limit: Optional[int] = None, offset: Optional[int] = None,
                 con: Optional[Connection] = None) -> list[model.Session]:
    clauses = []
    filter_clause, data = _filter_clause(session_id=session_id, date_range=date_range, is_live=is_live)
    if filter_clause:
        clauses.append(filter_clause)
    if order_clause := _order_clause(order_by=order_by):
        clauses.append(order_clause)
    if limit is not None:
        clauses.append("LIMIT :limit")
        data["limit"] = limit
    if offset is not None:
        clauses.append("OFFSET :offset")
        data["offset"] = offset

    clauses_str = " ".join(clauses)
    query = f"""
      SELECT id, date, start_time, end_time, venue_name, venue_url, is_live
      FROM sessions
      {clauses_str}
    """
    with get_connection(con) as con:
        cur = con.cursor()
        cur.execute(query, data)
        return list(map(_row_to_session, cur.fetchall()))


def delete_session_by_id(session_id: int, con: Optional[Connection] = None):
    query = """
      DELETE FROM sessions
      WHERE id = :id
    """
    data = dict(id=session_id)
    with get_connection(con) as con:
        cur = con.cursor()
        cur.execute(query, data)


def stop_live_sessions(con: Optional[Connection] = None):
    query = """
      UPDATE sessions SET is_live = false WHERE is_live = true
    """
    with get_connection(con) as con:
        cur = con.cursor()
        cur.execute(query)


def set_live_session(session_id: int, con: Optional[Connection] = None):
    query = """
      UPDATE sessions SET is_live = true WHERE id = :id
    """
    data = dict(id=session_id)
    with get_connection(con) as con:
        stop_live_sessions(con=con)
        cur = con.cursor()
        cur.execute(query, data)
