# ---
# jupyter:
#   jupytext:
#     formats: ipynb,py:percent
#     text_representation:
#       extension: .py
#       format_name: percent
#       format_version: '1.3'
#       jupytext_version: 1.19.1
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# %% [markdown]
# # File, JSON, And SQLite Workflows
#
# Research reproducibility starts with deterministic inputs. This notebook loads curated fixtures from disk, serializes representative objects with the JSON codec, and stores curve and portfolio snapshots in SQLite so a prior run can be replayed later.

# %%
from __future__ import annotations

from decimal import Decimal
from pathlib import Path
from tempfile import TemporaryDirectory

import matplotlib

matplotlib.use("Agg")

import matplotlib.pyplot as plt
import pandas as pd

from fuggers_py.core import Date
from fuggers_py.data import CurveId, CurvePoint, InstrumentId, PortfolioId, PricingSpec, QuoteSide
from fuggers_py.io import (
    CurveConfig,
    CurveSnapshot,
    InMemoryPortfolioStore,
    JsonCodec,
    OverrideRecord,
    Pagination,
    PricingConfig,
    SQLiteStorageAdapter,
    StoredPortfolio,
    StoredPosition,
)

from examples._shared.datasets import fixtures_root, load_fixture_market_data, load_fixture_reference_data
from examples._shared.plotting import apply_research_style, percent_axis
from examples._shared.tables import display_table

apply_research_style()

# %%
fixtures = fixtures_root()
market_data = load_fixture_market_data()
reference_data = load_fixture_reference_data()
quote = market_data.get_quote('US1234567890', QuoteSide.ASK)
curve_inputs = market_data.get_curve_inputs('usd.discount')
fixing = market_data.get_fixing('SOFR', Date.from_ymd(2026, 3, 13))
bond_reference = reference_data.get_bond_reference(InstrumentId('US1234567890'))

load_summary = pd.DataFrame(
    [
        {'object_type': 'Quote', 'identifier': 'US1234567890', 'detail': str(quote.value)},
        {'object_type': 'Curve inputs', 'identifier': curve_inputs.curve_id.as_str(), 'detail': str(len(curve_inputs.points))},
        {'object_type': 'Fixing', 'identifier': 'SOFR 2026-03-13', 'detail': str(fixing.value)},
        {'object_type': 'Bond reference', 'identifier': bond_reference.instrument_id.as_str(), 'detail': bond_reference.bond_type.value},
    ]
)
curve_snapshot = pd.DataFrame({'tenor_years': [float(point.tenor) for point in curve_inputs.points], 'zero_rate': [float(point.value) for point in curve_inputs.points]})

with TemporaryDirectory() as tmp_dir:
    db_path = Path(tmp_dir) / 'examples.sqlite'
    storage = SQLiteStorageAdapter(db_path)
    storage.bond_store.upsert_bond_reference(bond_reference)
    storage.curve_store.upsert_curve_config(
        CurveConfig(curve_id=CurveId('usd.discount'), curve_kind='zero', interpolation='linear', source='examples')
    )
    storage.curve_store.insert_curve_snapshot(
        CurveSnapshot(
            curve_id=CurveId('usd.discount'),
            reference_date=Date.from_ymd(2026, 3, 13),
            points=tuple(CurvePoint(point.tenor, point.value) for point in curve_inputs.points),
            source='examples',
        )
    )
    storage.config_store.upsert_pricing_config(
        PricingConfig(config_id='base', pricing_spec=PricingSpec(quote_side=QuoteSide.MID), source='examples')
    )
    storage.override_store.save_override(
        OverrideRecord(
            scope_id='US1234567890',
            field_name='spread',
            value='0.0010',
            numeric_value=Decimal('0.0010'),
            side=QuoteSide.MID,
            effective_date=Date.from_ymd(2026, 3, 13),
            source='manual',
        ),
        actor='notebook',
    )
    stored_bond = storage.get_bond_reference('US1234567890')
    stored_curve = storage.get_curve_snapshot('usd.discount')
    stored_config = storage.get_pricing_config('base')
    stored_overrides = storage.list_overrides('US1234567890', Pagination(limit=10))
    audit_entries = storage.list_audit_entries()
    storage.close()

portfolio_store = InMemoryPortfolioStore(
    [
        StoredPortfolio(
            portfolio_id=PortfolioId('example-portfolio'),
            positions=(
                StoredPosition(instrument_id=InstrumentId('US1234567890'), quantity=Decimal('100')),
                StoredPosition(instrument_id=InstrumentId('US0987654321'), weight=Decimal('0.35')),
            ),
            as_of=Date.from_ymd(2026, 3, 13),
            source='examples',
            name='Example Core',
        ),
        StoredPortfolio(
            portfolio_id=PortfolioId('example-portfolio'),
            positions=(
                StoredPosition(instrument_id=InstrumentId('US1234567890'), quantity=Decimal('110')),
                StoredPosition(instrument_id=InstrumentId('US1111111111'), weight=Decimal('0.20')),
            ),
            as_of=Date.from_ymd(2026, 3, 14),
            source='examples',
            name='Example Core',
        ),
    ]
)
latest_portfolio = portfolio_store.get_portfolio('example-portfolio')
codec = JsonCodec(indent=2)
encoded = codec.encode({'bond_reference': bond_reference, 'quote_side': QuoteSide.MID, 'curve_id': CurveId('usd.discount')})
decoded = codec.decode(encoded)

storage_summary = pd.DataFrame(
    [
        {'object_type': 'Curve points', 'count': len(curve_inputs.points)},
        {'object_type': 'Stored overrides', 'count': len(stored_overrides.items)},
        {'object_type': 'Audit entries', 'count': len(audit_entries.items)},
        {'object_type': 'Portfolio versions', 'count': len(portfolio_store.list_portfolios().items)},
    ]
)
repro_table = pd.DataFrame(
    [
        {'step': 'Fixtures loaded', 'result': f'{len(curve_inputs.points)} curve points and bond reference {bond_reference.instrument_id.as_str()}'},
        {'step': 'SQLite snapshot', 'result': stored_curve.curve_id.as_str()},
        {'step': 'JSON round-trip', 'result': decoded['curve_id'].as_str()},
        {'step': 'Latest portfolio version', 'result': str(latest_portfolio.as_of)},
    ]
)

# %%
display_table(load_summary)
display_table(storage_summary, float_columns=['count'])
display_table(repro_table)

# %% [markdown]
# The important point is not the technology choice. It is that the loaded inputs, the serialized artifacts, and the stored run state all line up tightly enough that a later notebook can reproduce the same analysis without hidden assumptions.

# %%
figure, axes = plt.subplots(1, 2, figsize=(12, 4.8))
axes[0].plot(curve_snapshot['tenor_years'], 100 * curve_snapshot['zero_rate'], marker='o', linewidth=2)
axes[0].set_title('Stored Curve Snapshot')
axes[0].set_xlabel('Tenor (years)')
axes[0].set_ylabel('Zero rate')
percent_axis(axes[0])

axes[1].bar(storage_summary['object_type'], storage_summary['count'])
axes[1].set_title('Storage Summary')
axes[1].set_xlabel('Stored object')
axes[1].set_ylabel('Count')
axes[1].tick_params(axis='x', rotation=20)
plt.tight_layout()
figure

# %%
figure, axis = plt.subplots(figsize=(10, 4.2))
audit_timeline = pd.DataFrame({'step': range(1, len(audit_entries.items) + 1), 'entries': [1] * len(audit_entries.items)})
axis.bar(audit_timeline['step'], audit_timeline['entries'])
axis.set_title('Audit/Event Timeline')
axis.set_xlabel('Audit record number')
axis.set_ylabel('Count')
plt.tight_layout()
figure

# %% [markdown]
# ## Reproducibility
#
# Keeping fixtures, JSON snapshots, and SQLite state aligned gives the research workflow a stable checkpoint. That matters when a desk wants to explain exactly which inputs produced a given pricing or portfolio result.
