"""
Terminal that mimics what a Point of Sale may operate like.
"""
import typer
import polars as pl
from retailtwin.cli.db import query_local_batches
from retailtwin.cli.pos.models import Base, Sync, Direction, Cart, Item, ItemOnCart
from retailtwin.utils import db_uri_from_session
from datetime import datetime

from sqlalchemy import create_engine, select
from sqlalchemy.orm import Session
from prompt_toolkit import PromptSession
from prompt_toolkit.completion import WordCompleter

# from prompt_toolkit.shortcuts import clear
from rich.console import Console
from rich.markdown import Markdown

console = Console()

HELP = """
# Retail twin POS

This is a simple terminal that simulates a dumb PoS with a single-line screen.

* **n**: Opens a new cart
* **l**: Lists carts
* **r**: Refreshes with the central database
* **q**: Quits the terminal
"""

CACHE_FILE_URL = "sqlite:///pos.db"

CONNECT_ERROR_MESSAGE = """
# ERROR

Could not sync to the central database.

The terminal will try to operate without a connection.
"""


def sync(session: Session, db_remote_uri: str, location: int):
    db_local_uri = db_uri_from_session(session)

    # Fetch data from the remote database
    try:
        inventory = query_local_batches(db_remote_uri, location)
    except:  # noqa: E722
        console.print(Markdown(CONNECT_ERROR_MESSAGE))
        return
    # The price and the discount is fixed by the latest received batch
    items = (
        inventory.select(
            [
                pl.col("upc"),
                pl.col("unitprice"),
                pl.col("discount_definition"),
                pl.col("discount_name"),
                pl.col("received"),
                pl.col("received").max().over("upc").alias("max_received"),
            ]
        )
        .filter(pl.col("received") == pl.col("max_received"))
        .select([pl.col("upc"), pl.col("unitprice"), pl.col("discount")])
        .with_columns(pl.col("unitprice").str.strip().cast(pl.Float32) * 100)
        .with_columns(pl.col("unitprice").cast(pl.Int32))
    )
    items.write_database(
        "items", db_local_uri, if_exists="replace", engine="sqlalchemy"
    )

    # Sync all customers
    (
        pl.read_database("select * from customers", db_remote_uri, engine="adbc")
        .select([pl.col("id"), pl.col("document")])
        .write_database(
            "customers", db_local_uri, if_exists="replace", engine="sqlalchemy"
        )
    )

    # Add last sync
    sync = Sync(direction=Direction.pull)
    session.add(sync)
    session.commit()

    # Sync unsynced carts
    unsynced_carts = pl.read_database(
        "select * from carts where synced = false and total_amount is not null",
        db_local_uri,
        engine="adbc",
    )

    if not unsynced_carts.is_empty():
        (
            unsynced_carts.select(
                [pl.col("checkout_dt").alias("checkout"), pl.col("customer")]
            )
            .with_columns(location=location)
            .write_database(
                "carts", db_remote_uri, if_exists="append", engine="sqlalchemy"
            )
        )

        # Query for items to sync
        query = """
        select
          c.id as cart,
          i.upc as upc,
          i.quantity
        from
          itemsoncart i
        join
          carts c
        on
          i.cart = c.id
        where
          c.synced = false"""

        pl.read_database(query, db_local_uri, engine="adbc").write_database(
            "itemsoncart", db_remote_uri, engine="sqlalchemy"
        )

        # Set synced to cart at the end of the process
        for cart in session.scalars(
            select(Cart).filter(Cart.synced == False)  # noqa: E712
        ).all():
            cart.synced = True

        session.commit()


def checkout(session: Session, cart_id: int):
    return None


def load_items(session: Session):
    return [str(it) for it in session.scalars(select(Item.upc)).all()]


def handle_command(command: str, cart_id: int, item_id: int, **kwargs):
    session = kwargs["session"]

    if command == "n":  # Start a new cart
        cart = Cart(checkin_dt=datetime.now(), synced=False)
        session.add(cart)
        session.commit()

        return cart.id, "", ""

    elif command == "l":  # List carts
        carts = pl.read_database(
            "select * from carts", db_uri_from_session(session), engine="adbc"
        )
        print(carts)
        return "", "", ""

    # Handle pushing items to the cart
    elif cart_id is not None and item_id:
        session.add(ItemOnCart(cart=cart_id, upc=item_id, quantity=command))
        print(f"{item_id} -- {command}")
        return cart_id, None, ""

    # Handle adding a new item
    elif cart_id is not None:
        # Check if the upc is in the database
        if command == "e":
            error = checkout(session=session, cart_id=cart_id)
            return None, None, error
        else:
            if session.scalar(select(Item).filter(Item.upc == int(command))):
                item_id = command
                print(f"selecting {item_id}")
                return cart_id, item_id, ""
            else:
                return cart_id, "", "ERROR: Code not found"

    else:
        cart_id, item_id if item_id else "", ""


def main(db_uri: str, location: int):
    console.print("Fetching data...")

    # Create the schema of the local database
    local_engine = create_engine(CACHE_FILE_URL)
    Base.metadata.create_all(local_engine)

    with Session(local_engine) as session:
        sync(session, db_uri, location)

        # Default completer with barcodes
        completer = WordCompleter(load_items(session))

        # Update the local copy of the data.
        console.print(Markdown(HELP))
        term = PromptSession()

        # State of the pos
        cart_id = ""
        item_id = ""
        error = ""

        while True:
            if cart_id and item_id:
                if error:
                    prompt = f"[{item_id}# quantity] !{error}!>"
                else:
                    prompt = f"[{item_id}# quantity]>"
            elif cart_id and not item_id:
                prompt = f"[{cart_id}# code, e for chekout]> "
            else:
                prompt = "#> "

            try:
                text = term.prompt(prompt, completer=completer)
            except KeyboardInterrupt:
                continue
            except EOFError:
                break
            else:
                if text in ["x", "q"]:
                    break
                elif text == "h":
                    console.print(Markdown(HELP))
                elif text == "r":
                    console.print("Refreshing...")
                    sync(session, db_uri, location)
                    completer = WordCompleter(load_items(session))
                else:
                    cart_id, item_id, error = handle_command(
                        text,
                        cart_id,
                        item_id,
                        location=location,
                        completer=completer,
                        db_remote_uri=db_uri,
                        session=session,
                    )
                    print(cart_id, item_id, error)

    print("GoodBye!")


app = typer.run(main)
