from __future__ import annotations
import sys; sys.path.append("..")  
from typing import Any, Dict, List, Tuple
from flask import Blueprint, jsonify, abort
from sqlalchemy.orm import Session
from sqlalchemy import func
from manager.MysqlManager import MysqlManager
from models.base_base import BaseBase
from models.deal_deal import DealDeal
from models.option_opti import OptionOpti

# The blueprint instance is created with the module name stripped of dots so
# registering it never triggers a ValueError.
blueprint = Blueprint(__name__.rsplit(".", 1)[-1], __name__)

# This function builds the full JSON response for a given base object id,
# returning None when the id does not exist.
def _build_base_response(session: Session, base_id: int) -> Dict[str, Any] | None:

    # This query loads the base record that matches the requested id or returns None when no match is found.
    base_record: BaseBase | None = (
        session.query(BaseBase)
        .filter_by(id_base=base_id)
        .one_or_none()
    )

    # This conditional branch exits early when the requested base id does not exist.
    if base_record is None:
        return None

    # This query loads all deal records that belong to the current base object.
    deal_records: List[DealDeal] = (
        session.query(DealDeal)
        .filter_by(base_deal=base_record.id_base)
        .all()
    )

    # This comprehension converts the SQLAlchemy deal objects into plain Python dictionaries ready for JSON serialization.
    deals: List[Dict[str, Any]] = [
        {
            "id":             d.id_deal,
            "provisiongroup": d.provisiongroup_deal,
            "providercode":   d.providercode_deal,
            "name":           d.name_deal,
            "alias":          d.alias_deal,
            "price":          float(d.price_deal),
            "starts":         d.starts_deal.isoformat() if d.starts_deal else None,
            "stops":          d.stops_deal.isoformat() if d.stops_deal else None,
            "provision1":     float(d.provision1_deal),
            "provision2":     float(d.provision2_deal),
            "provision3":     float(d.provision3_deal),
            "provision4":     float(d.provision4_deal),
            "created":        d.created_deal.isoformat() if d.created_deal else None,
            "updated":        d.updated_deal.isoformat() if d.updated_deal else None,
        }
        for d in deal_records
    ]

    # This query loads every option that belongs to the current base object.
    opti_records: List[OptionOpti] = (
        session.query(OptionOpti)
        .filter_by(base_opti=base_record.id_base)
        .all()
    )

    # The two collections below hold option nodes and category nodes so we can easily assemble the option hierarchy.
    option_nodes: List[Tuple[Dict[str, Any], str | None]] = []
    category_nodes: Dict[str, Dict[str, Any]] = {}

    # This loop converts each option database record into a node dictionary and remembers its parent relationship.
    for o in opti_records:

        # The node dictionary contains all option attributes plus a list for potential child options.
        node: Dict[str, Any] = {
            "id":             o.id_opti,
            "provisiongroup": o.provisiongroup_opti,
            "providercode":   o.providercode_opti,
            "name":           o.name_opti,
            "alias":          o.alias_opti,
            "price":          float(o.price_opti),
            "starts":         o.starts_opti.isoformat() if o.starts_opti else None,
            "stops":          o.stops_opti.isoformat() if o.stops_opti else None,
            "provision1":     float(o.provision1_opti),
            "provision2":     float(o.provision2_opti),
            "provision3":     float(o.provision3_opti),
            "provision4":     float(o.provision4_opti),
            "created":        o.created_opti.isoformat() if o.created_opti else None,
            "updated":        o.updated_opti.isoformat() if o.updated_opti else None,
            "items":          [],
        }

        # This conditional branch stores nodes whose provider code begins with “G” so children can later be attached to them.
        if o.providercode_opti.startswith("G"):
            category_nodes[o.providercode_opti] = node

        # Each tuple in option_nodes keeps the node itself and the code of its parent option or group.
        option_nodes.append((node, o.providercategory_opti))

    # This dictionary collects nodes that have no valid parent so they can be returned as top‑level entries grouped by parent code.
    root_nodes: Dict[str | None, List[Dict[str, Any]]] = {}

    # This loop attaches every node either to its parent category or to the root collection when no suitable parent exists.
    for node, parent_code in option_nodes:

        # This branch attaches a node to its parent when the parent exists and has been recognised as a category node.
        if parent_code and parent_code in category_nodes:
            category_nodes[parent_code]["items"].append(node)

        # This branch stores nodes without a valid parent as top‑level entries under their parent code.
        else:
            root_nodes.setdefault(parent_code, []).append(node)

    # This loop removes empty “items” lists from category nodes so the client does not receive useless empty arrays.
    for n in category_nodes.values():
        if not n["items"]:
            n.pop("items", None)

    # This comprehension builds the final list of option groups ready to be embedded into the JSON response.
    options: List[Dict[str, Any]] = [
        {"providercode": parent_code, "items": items}
        for parent_code, items in root_nodes.items()
    ]

    # This dictionary gathers the base table column values ready for JSON serialization.
    base_data: Dict[str, Any] = {
        "id":           base_record.id_base,
        "provider":     base_record.provider_base,
        "providercode": base_record.providercode_base,
        "name":         base_record.name_base,
        "alias":        base_record.alias_base,
        "flyerurl":     base_record.flyerurl_base,
        "piburl":       base_record.piburl_base,
        "created":      base_record.created_base.isoformat() if base_record.created_base else None,
        "updated":      base_record.updated_base.isoformat() if base_record.updated_base else None,
    }

    # This expression copies the JSON details column or uses an empty dictionary when the column is NULL.
    details_data: Dict[str, Any] = (
        base_record.details_base.copy()
        if base_record.details_base else {}
    )

    # This line removes the internal tariff_name helper key so it can be returned as a dedicated attribute.
    ai_identified_name = details_data.pop("tariff_name", None)

    # This dictionary merges the core base data with the JSON details and the AI‑identified name.
    merged_base = {
        **base_data,
        **details_data,
        "ai_identified_name": ai_identified_name,
    }

    # The function returns the assembled base, deal, and option data so the caller can serialize it to JSON.
    return {
        "base":    merged_base,
        "deals":   deals,
        "options": options,
    }

# This route returns an overview of every base tariff that currently has at
# least one active deal (stops_deal IS NULL).
@blueprint.route("/base", methods=["GET"])
def base_overview():

    # A new database session is opened through the MySQL manager.
    session = MysqlManager().getSession()
    try:

        # This query selects the distinct base objects that have at least one active deal.
        query = (
            session.query(
                BaseBase.id_base.label("id"),
                BaseBase.provider_base.label("provider"),
                BaseBase.providercode_base.label("providercode"),
                BaseBase.name_base.label("name"),
                BaseBase.alias_base.label("alias"),
            )
            .join(DealDeal, DealDeal.base_deal == BaseBase.id_base)
            .filter(DealDeal.stops_deal.is_(None))
            .group_by(BaseBase.id_base)
            .order_by(BaseBase.provider_base.asc())
        )

        # This comprehension converts every result row into a plain dictionary ready for JSON serialization.
        records: List[Dict[str, Any]] = [
            {
                "id":           row.id,
                "provider":     row.provider,
                "providercode": row.providercode,
                "name":         row.name,
                "alias":        row.alias,
            }
            for row in query.all()
        ]

    # The finally block guarantees that the session is always closed.
    finally:
        session.close()

    # The route returns the list of base objects as a JSON array.
    return jsonify(records)

# This route returns a complete JSON structure for a single base object or raises a 404 error when the id does not exist.
@blueprint.route("/base/<int:id>", methods=["GET"])
def base_details(id: int):

    # A new database session is opened through the MySQL manager.
    session = MysqlManager().getSession()
    try:

        # The helper function assembles the complete response structure or returns None when the id is unknown.
        data = _build_base_response(session, id)

    # The finally block guarantees that the session is always closed.
    finally:
        session.close()

    # This conditional branch aborts with a 404 status when the requested base id was not found.
    if data is None:
        abort(404, description=f"Base object with id={id} not found.")

    # The route returns the fully assembled JSON structure for the requested base id.
    return jsonify(data)
