import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../core/theme/app_theme.dart'; import '../../../core/network/api_client.dart'; import '../../asset_detail/repositories/asset_repository.dart'; import '../models/trade_model.dart'; import '../models/trade_acceptance_dto.dart'; import 'trade_execution_ai_plan_card.dart'; class TradeExecutionDialog { static const double _defaultPositionSize = 1000.0; static const double _defaultLeverage = 1.0; static const List _allowedInstruments = ['Stock', 'KnockOut', 'Option', 'CFD', 'Crypto']; static String _normalizeInstrumentType(String raw) { final clean = raw.toLowerCase().trim(); if (clean.contains('knock') || clean.contains('zertifikat') || clean.contains('turbo')) { return 'KnockOut'; } if (clean.contains('option')) { return 'Option'; } if (clean.contains('cfd')) { return 'CFD'; } if (clean.contains('crypto') || clean.contains('krypto')) { return 'Crypto'; } if (clean.contains('stock') || clean.contains('aktie') || clean.contains('etf')) { return 'Stock'; } return 'KnockOut'; } static void show( BuildContext context, { required TradeModel trade, required String defaultSymbol, bool isActive = false, required Function(TradeAcceptanceDto dto) onAccept, Function(String tradeId)? onReject, }) { final initEntry = trade.actualEntryPrice > 0 ? trade.actualEntryPrice : (trade.entryPrice > 0 ? trade.entryPrice : 100.0); final initPos = trade.positionSize > 0 ? trade.positionSize : _defaultPositionSize; final initLev = trade.leverageUsed > 0 ? trade.leverageUsed : _defaultLeverage; final calcQty = (initEntry > 0 && initPos > 0) ? (initPos / initEntry) : 10.0; final initQty = trade.quantity > 0 ? trade.quantity : calcQty; final actualEntryController = TextEditingController(text: initEntry.toStringAsFixed(2)); final positionSizeController = TextEditingController(text: initPos.toStringAsFixed(2)); final leverageController = TextEditingController(text: initLev.toStringAsFixed(1)); final quantityController = TextEditingController(text: initQty.toStringAsFixed(4)); final entryFeeController = TextEditingController(text: trade.entryFee.toStringAsFixed(2)); final exitFeeController = TextEditingController(text: trade.exitFee.toStringAsFixed(2)); final slController = TextEditingController(text: trade.stopLoss.toString()); final tpController = TextEditingController(text: trade.takeProfit.toString()); final derivativeIsinController = TextEditingController(text: trade.derivativeIsin); String selectedInstrumentType = _normalizeInstrumentType( trade.instrumentType.isNotEmpty ? trade.instrumentType : 'KnockOut', ); bool isFetchingDerivativePrice = false; void recalculateQuantity() { final entryStr = actualEntryController.text.replaceAll(',', '.').trim(); final posStr = positionSizeController.text.replaceAll(',', '.').trim(); final entry = double.tryParse(entryStr) ?? 0.0; final posSize = double.tryParse(posStr) ?? 0.0; if (entry > 0 && posSize > 0) { final q = posSize / entry; quantityController.text = q.toStringAsFixed(4); } } TradeAcceptanceDto buildDto() { final isinVal = trade.isin.isNotEmpty ? trade.isin : (trade.symbol.isNotEmpty ? trade.symbol : defaultSymbol); final symbolVal = trade.symbol.isNotEmpty ? trade.symbol : defaultSymbol; double parseNum(String text, double fallback) { final clean = text.replaceAll(',', '.').trim(); return double.tryParse(clean) ?? fallback; } return TradeAcceptanceDto( userId: trade.userId, tradeId: trade.id, analysisId: trade.analysisId, isin: isinVal, symbol: symbolVal, actualEntryPrice: parseNum(actualEntryController.text, trade.actualEntryPrice > 0 ? trade.actualEntryPrice : trade.entryPrice), positionSize: parseNum(positionSizeController.text, trade.positionSize > 0 ? trade.positionSize : 1000.0), leverageUsed: parseNum(leverageController.text, trade.leverageUsed > 0 ? trade.leverageUsed : 1.0), entryFee: parseNum(entryFeeController.text, trade.entryFee), exitFee: parseNum(exitFeeController.text, trade.exitFee), quantity: parseNum(quantityController.text, trade.quantity), executionTimestamp: DateTime.now().toUtc(), signalType: trade.signalType, entryPrice: trade.entryPrice, stopLoss: parseNum(slController.text, trade.stopLoss), takeProfit: parseNum(tpController.text, trade.takeProfit), instrumentType: selectedInstrumentType, derivativeIsin: derivativeIsinController.text.trim(), timeframe: trade.timeframe, reasoning: trade.reasoning, ); } Future fetchDerivativePrice(StateSetter setModalState, String inputIsin) async { final cleanIsin = inputIsin.trim().toUpperCase(); if (cleanIsin.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Bitte gib eine gültige Derivat/Knock-Out ISIN ein.'), backgroundColor: Colors.amber, behavior: SnackBarBehavior.floating), ); return; } setModalState(() => isFetchingDerivativePrice = true); try { final apiClient = context.read(); final assetRepo = AssetRepository(apiClient: apiClient); final technicals = await assetRepo.getAssetTechnical(cleanIsin, true); double? fetchedPrice; if (technicals != null) { if (technicals.candles.isNotEmpty) { fetchedPrice = technicals.candles.last.close; } else if (technicals.currentPrice != null && technicals.currentPrice! > 0) { fetchedPrice = technicals.currentPrice; } } if (fetchedPrice != null && fetchedPrice > 0) { actualEntryController.text = fetchedPrice.toStringAsFixed(2); recalculateQuantity(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Live-Kurs für Derivat $cleanIsin abgerufen: €${fetchedPrice.toStringAsFixed(2)}'), backgroundColor: AppTheme.primaryEmerald, behavior: SnackBarBehavior.floating, ), ); } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Kein Kurs für Derivat ISIN $cleanIsin gefunden.'), backgroundColor: Colors.amber, behavior: SnackBarBehavior.floating), ); } } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Fehler beim Abrufen des Kurses für $cleanIsin: $e'), backgroundColor: AppTheme.accentRed, behavior: SnackBarBehavior.floating), ); } finally { setModalState(() => isFetchingDerivativePrice = false); } } actualEntryController.addListener(recalculateQuantity); positionSizeController.addListener(recalculateQuantity); showDialog( context: context, builder: (dialogContext) { return StatefulBuilder( builder: (stfContext, setModalState) { final isKnockout = selectedInstrumentType.toLowerCase().contains('knock') || selectedInstrumentType.toLowerCase().contains('zertifikat') || selectedInstrumentType.toLowerCase().contains('option') || selectedInstrumentType.toLowerCase().contains('cfd'); final safeInstrumentValue = _allowedInstruments.contains(selectedInstrumentType) ? selectedInstrumentType : 'KnockOut'; return AlertDialog( backgroundColor: AppTheme.cardSurface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16), side: BorderSide(color: AppTheme.glassBorder)), title: Row( children: [ Icon(isActive ? Icons.tune : Icons.edit_note_outlined, color: AppTheme.primaryEmerald, size: 22), const SizedBox(width: 8), Expanded( child: Text( isActive ? 'Einstellungen für Trade #${trade.id}' : 'Trade-Ausführung & Parameter', style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold), ), ), ], ), content: SizedBox( width: 580, child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Trade-ID: ${trade.id} | Symbol/ISIN: ${trade.symbol.isNotEmpty ? trade.symbol : defaultSymbol}', style: TextStyle(color: AppTheme.textSecondary, fontSize: 12)), const SizedBox(height: 12), TradeExecutionAiPlanCard(trade: trade), const SizedBox(height: 16), const Text('Ihre Ausführungsdaten für das Depot:', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 10), DropdownButtonFormField( initialValue: safeInstrumentValue, dropdownColor: AppTheme.cardSurface, decoration: const InputDecoration(labelText: 'Finanzinstrument Typ', contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8)), items: const [ DropdownMenuItem(value: 'Stock', child: Text('Aktie / ETF (Direktinvestment)', style: TextStyle(color: Colors.white, fontSize: 13))), DropdownMenuItem(value: 'KnockOut', child: Text('Knock-Out Zertifikat', style: TextStyle(color: Colors.white, fontSize: 13))), DropdownMenuItem(value: 'Option', child: Text('Optionsschein / Derivat', style: TextStyle(color: Colors.white, fontSize: 13))), DropdownMenuItem(value: 'CFD', child: Text('CFD (Hebel-Derivat)', style: TextStyle(color: Colors.white, fontSize: 13))), DropdownMenuItem(value: 'Crypto', child: Text('Krypto', style: TextStyle(color: Colors.white, fontSize: 13))), ], onChanged: (val) { if (val != null) setModalState(() => selectedInstrumentType = val); }, ), const SizedBox(height: 10), if (isKnockout) ...[ Row( children: [ Expanded( child: TextField( controller: derivativeIsinController, decoration: const InputDecoration(labelText: 'Knock-Out / Derivat ISIN (z.B. DE000...)', hintText: 'ISIN des Hebels eingeben...', contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8)), ), ), const SizedBox(width: 8), ElevatedButton.icon( onPressed: isFetchingDerivativePrice ? null : () => fetchDerivativePrice(setModalState, derivativeIsinController.text), icon: isFetchingDerivativePrice ? const SizedBox(width: 14, height: 14, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.black)) : const Icon(Icons.bolt, size: 16), label: const Text('tr_GetPrice'), style: ElevatedButton.styleFrom(backgroundColor: AppTheme.accentCyan, foregroundColor: Colors.black, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12)), ), ], ), const SizedBox(height: 10), ], Row( children: [ Expanded( child: TextField( controller: actualEntryController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration(labelText: 'Tatsächlicher Einstiegskurs (€)', contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8)), ), ), const SizedBox(width: 8), Expanded( child: TextField( controller: positionSizeController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration(labelText: 'Investitionsvolumen (€)', contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8)), ), ), ], ), const SizedBox(height: 10), Row( children: [ Expanded( child: TextField( controller: leverageController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration(labelText: 'Genutzter Hebel (x)', contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8)), ), ), const SizedBox(width: 8), Expanded( child: TextField( controller: quantityController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration(labelText: 'Stückzahl (Invest. / Einstieg)', contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8)), ), ), ], ), const SizedBox(height: 10), Row( children: [ Expanded( child: TextField( controller: entryFeeController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration(labelText: 'Einstiegsgebühr (€)', contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8)), ), ), const SizedBox(width: 8), Expanded( child: TextField( controller: exitFeeController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration(labelText: 'Ausstiegsgebühr (€)', contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8)), ), ), ], ), const SizedBox(height: 10), Row( children: [ Expanded( child: TextField( controller: slController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration(labelText: 'Stop-Loss (€)', contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8)), ), ), const SizedBox(width: 8), Expanded( child: TextField( controller: tpController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration(labelText: 'Take-Profit (€)', contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8)), ), ), ], ), ], ), ), ), actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext), child: Text('Abbrechen', style: TextStyle(color: AppTheme.textMuted)), ), if (!isActive && onReject != null) OutlinedButton.icon( onPressed: () { onReject(trade.id); Navigator.pop(dialogContext); }, icon: Icon(Icons.cancel, color: AppTheme.accentRed, size: 16), label: Text('Trade Ablehnen', style: TextStyle(color: AppTheme.accentRed)), style: OutlinedButton.styleFrom(side: BorderSide(color: AppTheme.accentRed)), ), if (!isActive && onReject != null) const SizedBox(width: 8), ElevatedButton.icon( onPressed: () { final dto = buildDto(); onAccept(dto); Navigator.of(dialogContext).pop(); }, icon: Icon(isActive ? Icons.save : Icons.check_circle, size: 16), label: Text(isActive ? 'Einstellungen Speichern' : 'Trade Annehmen & Ausführen'), style: ElevatedButton.styleFrom( backgroundColor: isActive ? AppTheme.accentCyan : AppTheme.primaryEmerald, foregroundColor: Colors.black, ), ), ], ); }, ); }, ); } }