# -*- coding: utf-8 -*-
"""
Flask aplikace pro výpočet realizovaných zisků z kryptoměn metodou FIFO (CZ pravidla).

✅ Funkce
- Ukládá transakce do SQLite (soubor `krypto.db`).
- Podporované typy: BUY (nákup), SELL (prodej). (SWAP viz poznámka níže)
- Vše v CZK: u každé transakce zadáváte celkovou hodnotu v Kč v daný den (včetně poplatků).
- Výpočet FIFO realizovaného zisku pro každé SELL (po datech) + souhrn za zvolené období.
- Export CSV.

ℹ️ Poznámka ke SWAPům (krypto → krypto):
  V ČR se na SWAP pohlíží jako na prodej (z pohledu daní). Zadejte tedy dvě transakce:
   1) SELL původního aktiva s hodnotou vyjádřenou v Kč v okamžiku směny (pro výpočet zisku)
   2) BUY cílového aktiva s pořizovací cenou v Kč (stejnou částkou)

Spuštění:
    $ pip install flask sqlalchemy pandas
    $ python app.py

Aplikace poběží na http://127.0.0.1:5000

Autor: ChatGPT (CZ)
"""

from __future__ import annotations
from decimal import Decimal, ROUND_HALF_UP, getcontext
from datetime import datetime, date
from typing import List, Dict, Any, Tuple

from flask import Flask, request, redirect, url_for, Response, render_template_string, jsonify
from sqlalchemy import (
    create_engine, Column, Integer, String, Date, Numeric, Text
)
from sqlalchemy.orm import sessionmaker, declarative_base
import csv
import io

# Nastavení přesnosti pro Decimal
getcontext().prec = 28

app = Flask(__name__)

# --- DB ---
DB_URL = "sqlite:///krypto.db"
engine = create_engine(DB_URL, future=True)
SessionLocal = sessionmaker(bind=engine)
Base = declarative_base()

class Tx(Base):
    __tablename__ = "transactions"
    id = Column(Integer, primary_key=True)
    date = Column(Date, nullable=False)
    ttype = Column(String(8), nullable=False)  # BUY | SELL
    asset = Column(String(32), nullable=False) # např. BTC, ETH
    quantity = Column(Numeric(24, 10), nullable=False)  # množství krypto
    value_czk = Column(Numeric(18, 2), nullable=False)  # celková hodnota v Kč (včetně poplatků)
    fee_czk = Column(Numeric(18, 2), nullable=True)     # dodatečné poplatky v Kč (pokud nejsou v value_czk)
    note = Column(Text, nullable=True)

Base.metadata.create_all(bind=engine)

# --- Pomocné ---

def D(x: Any) -> Decimal:
    if isinstance(x, Decimal):
        return x
    return Decimal(str(x))

CZK = Decimal('0.01')

# --- FIFO výpočet ---

def fifo_realized_gains(transactions: List[Tx], asset_filter: str | None = None,
                         date_from: date | None = None, date_to: date | None = None) -> Tuple[List[Dict[str, Any]], Dict[str, Any]]:
    """Vypočítá realizované zisky (SELL) metodou FIFO.

    Vrací tuple (rows, summary):
      rows: seznam záznamů pro každý SELL s detailními položkami FIFO a výsledným ziskem
      summary: souhrn za období (proceeds, cost, gain) i po aktivech
    """
    # Filtrovat
    txs = []
    for t in transactions:
        if asset_filter and t.asset.upper() != asset_filter.upper():
            continue
        if date_from and t.date < date_from:
            continue
        if date_to and t.date > date_to:
            continue
        txs.append(t)

    # Setřídit chronologicky
    txs.sort(key=lambda x: (x.date, x.id))

    # FIFO zásobník lotů pro každé aktivum
    lots: Dict[str, List[Dict[str, Decimal]]] = {}

    results: List[Dict[str, Any]] = []

    for t in txs:
        asset = t.asset.upper()
        qty = D(t.quantity)
        val = D(t.value_czk)
        fee = D(t.fee_czk or 0)
        total_val = val + fee  # poplatek přičítáme k nákupu/prodeji dle potřeby

        if t.ttype.upper() == 'BUY':
            # pořizovací cena na jednotku
            unit_cost = (total_val / qty) if qty != 0 else D(0)
            lots.setdefault(asset, []).append({
                'date': D(0),  # nepoužito v kalkulaci, ale možno rozšířit
                'qty': qty,
                'unit_cost': unit_cost,
            })
        elif t.ttype.upper() == 'SELL':
            if asset not in lots:
                lots[asset] = []
            remain_to_sell = qty
            proceeds = total_val  # celkový výnos v Kč (můžete zadat poplatek záporně do fee, pokud jej chcete odečíst)

            consumed: List[Dict[str, Any]] = []
            total_cost = D(0)

            # FIFO čerpání
            while remain_to_sell > 0 and lots[asset]:
                lot = lots[asset][0]
                take = min(remain_to_sell, lot['qty'])
                cost = (lot['unit_cost'] * take).quantize(CZK, rounding=ROUND_HALF_UP)
                consumed.append({'from_qty': take, 'unit_cost': lot['unit_cost'], 'cost': cost})
                total_cost += cost
                lot['qty'] -= take
                remain_to_sell -= take
                if lot['qty'] <= D('0.0000000001'):
                    lots[asset].pop(0)

            if remain_to_sell > 0:
                # Prodej víc než máme – označíme jako varování
                warning = f"UPOZORNĚNÍ: Prodáno {qty} {asset}, ale k dispozici bylo méně (chybí {remain_to_sell})."
            else:
                warning = None

            gain = (proceeds - total_cost).quantize(CZK, rounding=ROUND_HALF_UP)

            results.append({
                'date': t.date.isoformat(),
                'asset': asset,
                'sell_qty': str(qty),
                'proceeds_czk': float(proceeds),
                'cost_czk': float(total_cost),
                'gain_czk': float(gain),
                'fifo_lots': [{'qty': float(x['from_qty']), 'unit_cost': float(x['unit_cost']), 'cost': float(x['cost'])} for x in consumed],
                'warning': warning,
            })
        else:
            # Neznámý typ
            pass

    # Souhrn
    summary_total = {
        'proceeds_czk': float(sum(D(r['proceeds_czk']) for r in results)),
        'cost_czk': float(sum(D(r['cost_czk']) for r in results)),
        'gain_czk': float(sum(D(r['gain_czk']) for r in results)),
    }
    # Souhrn po aktivech
    by_asset: Dict[str, Dict[str, float]] = {}
    for r in results:
        a = r['asset']
        by_asset.setdefault(a, {'proceeds_czk': 0.0, 'cost_czk': 0.0, 'gain_czk': 0.0})
        by_asset[a]['proceeds_czk'] += r['proceeds_czk']
        by_asset[a]['cost_czk'] += r['cost_czk']
        by_asset[a]['gain_czk'] += r['gain_czk']

    summary = { 'total': summary_total, 'by_asset': by_asset }
    return results, summary

# --- HTML šablona ---
PAGE = """
<!doctype html>
<html lang="cs">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Krypto daně FIFO (CZK)</title>
<style>
body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Arial,sans-serif;max-width:1100px;margin:2rem auto;padding:0 1rem}
header{display:flex;justify-content:space-between;align-items:center}
.card{border:1px solid #ddd;border-radius:14px;padding:1rem;margin:1rem 0}
label{display:block;margin:.25rem 0 .1rem}
input,select,button,textarea{padding:.5rem;border:1px solid #bbb;border-radius:10px;width:100%;box-sizing:border-box}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:1rem}
.table{width:100%;border-collapse:collapse}
.table th,.table td{border-bottom:1px solid #eee;padding:.5rem .4rem;text-align:left}
small{color:#555}
pre{white-space:pre-wrap;word-wrap:break-word}
.badge{display:inline-block;background:#eef;padding:.2rem .5rem;border-radius:8px}
footer{margin:2rem 0;color:#666}
</style>
</head>
<body>
<header>
  <h1>Výpočet daně z krypta – FIFO (CZK, SQLite)</h1>
  <nav>
    <a href="{{ url_for('export_csv') }}">Export CSV</a>
  </nav>
</header>

<div class="card">
  <h2>Přidat transakci</h2>
  <form method="post" action="{{ url_for('add_tx') }}" class="grid">
    <div>
      <label>Datum</label>
      <input type="date" name="date" required>
    </div>
    <div>
      <label>Typ</label>
      <select name="ttype" required>
        <option value="BUY">BUY (nákup)</option>
        <option value="SELL">SELL (prodej)</option>
      </select>
    </div>
    <div>
      <label>Asset (např. BTC, ETH)</label>
      <input type="text" name="asset" placeholder="BTC" required>
    </div>
    <div>
      <label>Množství</label>
      <input type="number" step="0.00000001" name="quantity" required>
    </div>
    <div>
      <label>Hodnota v Kč (celkem)</label>
      <input type="number" step="0.01" name="value_czk" required>
    </div>
    <div>
      <label>Poplatek v Kč (volitelné)</label>
      <input type="number" step="0.01" name="fee_czk">
    </div>
    <div style="grid-column:1/-1">
      <label>Poznámka</label>
      <input type="text" name="note" placeholder="Burza, odkaz na výpis..."></nput>
    </div>
    <div style="grid-column:1/-1">
      <button type="submit">Uložit</button>
      <small>U SWAPu zadejte SELL původního aktiva (v Kč) a samostatně BUY cílového aktiva (se stejnou Kč částkou).</small>
    </div>
  </form>
</div>

<div class="card">
  <h2>Transakce</h2>
  <table class="table">
    <thead>
      <tr><th>ID</th><th>Datum</th><th>Typ</th><th>Asset</th><th>Množství</th><th>Hodnota Kč</th><th>Poplatek Kč</th><th>Poznámka</th><th></th></tr>
    </thead>
    <tbody>
      {% for t in txs %}
      <tr>
        <td>{{ t.id }}</td>
        <td>{{ t.date }}</td>
        <td><span class="badge">{{ t.ttype }}</span></td>
        <td>{{ t.asset }}</td>
        <td>{{ '%.8f'|format(t.quantity) }}</td>
        <td>{{ '%.2f'|format(t.value_czk) }}</td>
        <td>{{ '%.2f'|format(t.fee_czk or 0) }}</td>
        <td>{{ t.note or '' }}</td>
        <td><a href="{{ url_for('delete_tx', tx_id=t.id) }}" onclick="return confirm('Smazat transakci?');">🗑️</a></td>
      </tr>
      {% endfor %}
    </tbody>
  </table>
</div>

<div class="card">
  <h2>Výpočet FIFO</h2>
  <form method="get" action="{{ url_for('gains_view') }}" class="grid">
    <div>
      <label>Asset (volitelně)</label>
      <input type="text" name="asset" placeholder="BTC" value="{{ request.args.get('asset','') }}">
    </div>
    <div>
      <label>Od data</label>
      <input type="date" name="date_from" value="{{ request.args.get('date_from','') }}">
    </div>
    <div>
      <label>Do data</label>
      <input type="date" name="date_to" value="{{ request.args.get('date_to','') }}">
    </div>
    <div style="align-self:end"><button type="submit">Spočítat</button></div>
  </form>

  {% if results is defined %}
    <h3>Souhrn</h3>
    <p><b>Výnosy:</b> {{ '%.2f'|format(summary.total.proceeds_czk) }} Kč,
       <b>Náklady:</b> {{ '%.2f'|format(summary.total.cost_czk) }} Kč,
       <b>Zisk:</b> {{ '%.2f'|format(summary.total.gain_czk) }} Kč</p>

    <details open>
      <summary><b>Podle aktiv</b></summary>
      <table class="table">
        <thead><tr><th>Asset</th><th>Výnosy Kč</th><th>Náklady Kč</th><th>Zisk Kč</th></tr></thead>
        <tbody>
        {% for a, row in summary.by_asset.items() %}
          <tr><td>{{ a }}</td><td>{{ '%.2f'|format(row.proceeds_czk) }}</td><td>{{ '%.2f'|format(row.cost_czk) }}</td><td>{{ '%.2f'|format(row.gain_czk) }}</td></tr>
        {% endfor %}
        </tbody>
      </table>
    </details>

    <h3>Detailní SELL transakce</h3>
    <table class="table">
      <thead>
        <tr><th>Datum</th><th>Asset</th><th>Množství</th><th>Výnos Kč</th><th>Náklady Kč (FIFO)</th><th>Zisk Kč</th><th>FIFO rozpad</th><th>Pozn.</th></tr>
      </thead>
      <tbody>
        {% for r in results %}
        <tr>
          <td>{{ r.date }}</td>
          <td>{{ r.asset }}</td>
          <td>{{ r.sell_qty }}</td>
          <td>{{ '%.2f'|format(r.proceeds_czk) }}</td>
          <td>{{ '%.2f'|format(r.cost_czk) }}</td>
          <td><b>{{ '%.2f'|format(r.gain_czk) }}</b></td>
          <td>
            {% for lot in r.fifo_lots %}
              <div>{{ '%.8f'|format(lot.qty) }} × {{ '%.2f'|format(lot.unit_cost) }} Kč = {{ '%.2f'|format(lot.cost) }} Kč</div>
            {% endfor %}
          </td>
          <td>{{ r.warning or '' }}</td>
        </tr>
        {% endfor %}
      </tbody>
    </table>
  {% endif %}
</div>

<footer>
  <p><b>Tipy:</b> U SWAPu (krypto→krypto) zadejte SELL původního aktiva v Kč a samostatně BUY cílového aktiva se stejnou Kč hodnotou. Pro dlouhodobé držení doporučuji evidovat poplatky a zahrnout je do hodnoty.</p>
</footer>

</body></html>
"""

# --- Routes ---

@app.route("/")
def index():
    with SessionLocal() as db:
        txs = db.query(Tx).order_by(Tx.date, Tx.id).all()
    return render_template_string(PAGE, txs=txs)

@app.route("/add", methods=["POST"])
def add_tx():
    form = request.form
    try:
        d = datetime.strptime(form['date'], "%Y-%m-%d").date()
        ttype = form['ttype'].upper()
        asset = form['asset'].upper().strip()
        qty = D(form['quantity'])
        val = D(form['value_czk'])
        fee = D(form.get('fee_czk') or '0')
        note = form.get('note')
        with SessionLocal() as db:
            tx = Tx(date=d, ttype=ttype, asset=asset, quantity=qty, value_czk=val, fee_czk=fee, note=note)
            db.add(tx)
            db.commit()
        return redirect(url_for('index'))
    except Exception as e:
        return Response(f"Chyba při ukládání: {e}", status=400)

@app.route("/delete/<int:tx_id>")
def delete_tx(tx_id: int):
    with SessionLocal() as db:
        t = db.query(Tx).get(tx_id)
        if t:
            db.delete(t)
            db.commit()
    return redirect(url_for('index'))

@app.route("/gains")
def gains_view():
    asset = request.args.get('asset') or None
    date_from = request.args.get('date_from')
    date_to = request.args.get('date_to')
    dfrom = datetime.strptime(date_from, "%Y-%m-%d").date() if date_from else None
    dto = datetime.strptime(date_to, "%Y-%m-%d").date() if date_to else None

    with SessionLocal() as db:
        txs = db.query(Tx).all()
    results, summary = fifo_realized_gains(txs, asset_filter=asset, date_from=dfrom, date_to=dto)

    # Umožní zobrazení na hlavní stránce s výsledky
    with SessionLocal() as db:
        txs_all = db.query(Tx).order_by(Tx.date, Tx.id).all()
    return render_template_string(PAGE, txs=txs_all, results=results, summary=SimpleNamespaceDict(summary))

@app.route("/api/gains")
def gains_api():
    with SessionLocal() as db:
        txs = db.query(Tx).all()
    results, summary = fifo_realized_gains(txs)
    return jsonify({"results": results, "summary": summary})

@app.route("/export.csv")
def export_csv():
    with SessionLocal() as db:
        txs = db.query(Tx).order_by(Tx.date, Tx.id).all()
    output = io.StringIO()
    writer = csv.writer(output)
    writer.writerow(["id","date","type","asset","quantity","value_czk","fee_czk","note"])
    for t in txs:
        writer.writerow([t.id, t.date.isoformat(), t.ttype, t.asset, f"{D(t.quantity):.8f}", f"{D(t.value_czk):.2f}", f"{D(t.fee_czk or 0):.2f}", t.note or ""])    
    csv_data = output.getvalue()
    return Response(csv_data, mimetype='text/csv', headers={'Content-Disposition': 'attachment; filename=transactions.csv'})

# Pomocný wrapper, aby šlo v Jinja přistupovat tečkou (summary.total.proceeds_czk)
class SimpleNamespaceDict(dict):
    def __getattr__(self, item):
        val = self[item]
        return SimpleNamespaceDict(val) if isinstance(val, dict) else val

if __name__ == "__main__":
    app.run(debug=True)
