using System;
using System.IO;
using System.Text.Json;
using System.Threading;
namespace AutoBidder.Utilities
{
public class AppSettings
{
///
/// Millisecondi di anticipo sulla scadenza con cui parte la puntata.
///
/// È il parametro che decide quanto costa vincere, e i dossier
/// raccolti dicono esattamente dove sta l'ottimo. Il motore punta solo nei cicli
/// che arrivano fino a questo anticipo senza che nessun altro abbia puntato:
/// più l'anticipo è largo, più cicli lo raggiungono, più puntate si spendono.
///
/// Rigiocando 2.095 dossier (1.007.850 cicli) con backtest.ps1, i cicli
/// che scendono fino a una data soglia sono:
///
/// - sotto 1 s - 0,6% dei cicli, mediana 1 puntata per asta
/// - sotto 2 s - 5,9%, mediana 15 puntate
/// - sotto 3 s - 20,4%, mediana 46 puntate
/// - sotto 4 s - 79,5%, mediana 97 puntate
///
///
/// Il grosso degli avversari punta con circa tre secondi ancora sul cronometro:
/// il 58,8% dei cicli si ferma esattamente li'. Sotto i due secondi la concorrenza si
/// dirada, ed e' il motivo per cui l'anticipo va tenuto stretto - allargarlo di un
/// secondo moltiplica per dieci le puntate spese.
///
/// Dal lato opposto c'e' la rete: ping mediano 56 ms, p99 135 ms, p99,9
/// 444 ms, con punte oltre i due secondi. Sotto i 600 ms di anticipo basta un picco
/// perche' la puntata arrivi a giochi chiusi, e una puntata tardiva costa l'asta intera.
///
/// Predefinito 1000 ms: il valore piu' largo che resta nella fascia da
/// una puntata per asta, e piu' del doppio del p99,9 del ping. Sotto il secondo i
/// dossier non possono dire di piu', perche' Bidoo dichiara la scadenza al secondo
/// intero.
///
///
/// Aggiornato a 500 ms. Le due ragioni per stare larghi sono cadute
/// entrambe. La prima — il p99,9 del ping a 444 ms — era misurata quando il tetto di
/// 40 richieste al secondo faceva accodare le chiamate: con il tetto tolto il ping
/// misurato sui dossier nuovi sta fra 47 e 78 ms, mediana 55. La seconda era la
/// paura della concorrenza fitta sotto i due secondi, ma i tipi di puntata dicono
/// il contrario: il 75,3% delle puntate avversarie è automatica e scatta a 2 s
/// esatti, quindi sotto quella soglia resta solo chi punta a mano.
///
/// A 500 ms la puntata parte con circa nove volte il ping di margine, e cade
/// nella metà di secondo in cui l'autopuntata del sito ha già sparato. Più in là non
/// conviene andare: Bidoo dichiara la scadenza al secondo intero, quindi sotto i
/// 500 ms si starebbe scommettendo sull'arrotondamento.
///
public int DefaultBidBeforeDeadlineMs { get; set; } = 500;
public double DefaultMinPrice { get; set; } = 0;
public double DefaultMaxPrice { get; set; } = 0;
public int DefaultMaxClicks { get; set; } = 0;
public int DefaultMinResets { get; set; } = 0;
public int DefaultMaxResets { get; set; } = 0;
///
/// Mostra avviso quando una puntata arriva troppo tardi (timer scaduto).
/// Suggerisce all'utente di aumentare il tempo di puntata.
/// Default: true
///
public bool ShowLateBidWarning { get; set; } = true;
// ═══════════════════════════════════════════════════════════════════
// MOTORE DI PRECISIONE — CADENZA POLLING ADATTIVA
// ═══════════════════════════════════════════════════════════════════
// Ogni asta ha il proprio anello di polling, che si stringe man mano che
// la scadenza si avvicina: un'asta a otto minuti non ha bisogno di quattro
// chiamate al secondo, una a tre secondi sì.
/// Cadenza polling oltre i 60 s dalla scadenza. Default: 2000 ms.
public int PollIntervalFarMs { get; set; } = 2000;
/// Cadenza polling tra 10 e 60 s dalla scadenza. Default: 900 ms.
public int PollIntervalMidMs { get; set; } = 900;
/// Cadenza polling sotto i 10 s dalla scadenza. Default: 400 ms.
public int PollIntervalNearMs { get; set; } = 400;
///
/// Cadenza polling nella finestra critica, solo per le aste in stato Attiva.
/// In sola osservazione non c'è puntata da azzeccare e si risparmiano chiamate.
/// Default: 220 ms.
///
public int PollIntervalCriticalMs { get; set; } = 220;
/// Ampiezza della finestra critica prima della scadenza. Default: 4000 ms.
public int CriticalWindowMs { get; set; } = 4000;
///
/// Tetto complessivo di richieste al secondo verso Bidoo (le puntate non sono soggette
/// al limite: hanno corsia preferenziale). Default: 40.
///
///
/// Tetto di richieste al secondo verso Bidoo. 0 = nessun tetto, ed è il
/// predefinito: il ritmo lo detta la rete, e a frenare è il server con 429/503.
///
/// Il vecchio predefinito era 40, ed era il vero collo di bottiglia. Misurato
/// sui registri di tre giorni: con fino a 31 aste seguite insieme la distribuzione
/// delle richieste era tagliata netta sul tetto (p90 39, p99 41), e ogni asta
/// veniva interrogata ogni ~596 ms invece dei 220 configurati per la finestra
/// critica. Con il timer a un secondo restava un solo sguardo prima della
/// scadenza: da lì le puntate perse e il tempo che non scorreva.
///
public double MaxRequestsPerSecond { get; set; } = 0;
///
/// Porta la granularità del timer di sistema a 1 ms mentre il monitor è in funzione.
/// Senza, ogni attesa può sforare di ~15 ms. Default: true.
///
public bool PrecisionTimerEnabled { get; set; } = true;
// ═══════════════════════════════════════════════════════════════════
// ASTE PROGRAMMATE — attesa dell'inizio
// ═══════════════════════════════════════════════════════════════════
// Un'asta che comincia fra tre ore non va interrogata ogni due secondi, ma
// all'apertura il motore dev'essere già pronto. La cadenza si allarga in
// proporzione all'attesa e si stringe man mano che l'inizio si avvicina.
/// Attiva la cadenza rilassata per le aste non ancora cominciate.
public bool ScheduledAuctionBackoffEnabled { get; set; } = true;
/// Cadenza quando l'inizio è oltre un'ora. Default: 20 minuti.
public int ScheduledPollFarSeconds { get; set; } = 1200;
/// Cadenza quando l'inizio è fra 10 e 60 minuti. Default: 2 minuti.
public int ScheduledPollMidSeconds { get; set; } = 120;
///
/// Quanto prima dell'inizio si torna alla cadenza normale. Default: 120 s —
/// un margine ampio garantisce di non perdere l'apertura.
///
public int ScheduledPollWakeSeconds { get; set; } = 120;
// ═══════════════════════════════════════════════════════════════════
// ANTICIPO PUNTATA — misura e taratura
// ═══════════════════════════════════════════════════════════════════
///
/// Registra l'anticipo effettivo di ogni puntata, per capire se il valore
/// impostato è quello giusto. Default: true — è solo una misura, non cambia nulla.
///
public bool BidLeadTrackingEnabled { get; set; } = true;
///
/// Propone (non applica) una correzione dell'anticipo quando i dati raccolti
/// mostrano uno scarto sistematico. Default: true.
///
public bool BidLeadSuggestionsEnabled { get; set; } = true;
/// Misure necessarie prima di azzardare una proposta. Default: 15.
public int BidLeadMinSamples { get; set; } = 15;
// ═══════════════════════════════════════════════════════════════════
// NOTIFICHE
// ═══════════════════════════════════════════════════════════════════
/// Mostra una notifica di Windows quando un'asta viene vinta.
public bool NotifyOnWin { get; set; } = true;
/// Notifica anche quando un'asta seguita viene persa. Default: false.
public bool NotifyOnLoss { get; set; } = false;
// ═══════════════════════════════════════════════════════════════════
// CARTELLE DEI DATI
// ═══════════════════════════════════════════════════════════════════
// Vuoto = posizione predefinita sotto %LocalAppData%\AutoBidder.
/// Dati d'esercizio: aste monitorate e prodotti seguiti.
public string DataFolder { get; set; } = "";
///
/// Statistiche ed esportazioni. Separata perché cresce nel tempo e serve
/// soprattutto per le analisi fatte fuori dall'applicazione.
///
public string StatsFolder { get; set; } = "";
///
/// Registri di testo: log applicativo, riscatto puntate, dossier delle aste.
/// Vuoto = Dati\Registri.
///
public string LogFolder { get; set; } = "";
// ═══════════════════════════════════════════════════════════════════
// REGISTRI SU FILE
// ═══════════════════════════════════════════════════════════════════
/// Scrive il log applicativo anche su file, un file per giorno.
public bool WriteAppLogFile { get; set; } = true;
/// Scrive su file il registro del riscatto puntate.
public bool WriteFreeBidsLogFile { get; set; } = true;
///
/// Registra un dossier per ogni asta seguita: intestazione, eventi e riepilogo, in
/// formato JSON Lines. È il materiale su cui si studiano le strategie, e va raccolto
/// mentre l'asta è in corso — a posteriori Bidoo non espone nulla di tutto questo.
///
public bool WriteAuctionDossiers { get; set; } = true;
///
/// Registra nel dossier ogni singola interrogazione a Bidoo, non solo gli
/// eventi. È la risoluzione massima — prezzo, timer e ping a ogni poll — e il prezzo
/// da pagare è la dimensione: nella finestra critica sono quattro righe al secondo,
/// quindi un'asta lunga vale qualche megabyte.
///
public bool DossierIncludeRawPolls { get; set; } = true;
///
/// Dopo quanti giorni cancellare i registri. 0 = tenerli per sempre.
/// Predefinito 90: con i poll grezzi accesi lo spazio va tenuto d'occhio.
///
public int LogRetentionDays { get; set; } = 90;
///
/// Registra una scheda dettagliata per ogni asta conclusa (serie dei prezzi,
/// puntate per utente, durata). È la materia prima per migliorare le strategie.
///
public bool DetailedStatsEnabled { get; set; } = true;
// ═══════════════════════════════════════════════════════════════════
// CATALOGO — cache
// ═══════════════════════════════════════════════════════════════════
///
/// Per quanti secondi riusare le aste già scaricate di una categoria.
/// Ogni caricamento costa una richiesta ogni cinquanta aste: senza cache,
/// tornare avanti e indietro fra due categorie le rifà tutte ogni volta.
/// Default: 60 s. 0 = nessuna cache.
///
public int CatalogCacheSeconds { get; set; } = 60;
// ═══════════════════════════════════════════════════════════════════
// ESPLORA — CATALOGO
// ═══════════════════════════════════════════════════════════════════
///
/// Quante aste caricare al massimo per categoria. Il listato si sfoglia a pagine
/// di cinquanta, in ordine di scadenza: il tetto taglia via le aste più lontane,
/// mai quelle imminenti. Le categorie piccole finiscono prima e si fermano da sole.
/// Default: 500 (dieci pagine).
///
public int CatalogMaxAuctions { get; set; } = 500;
///
/// Aggiorna prezzi e timer delle schede mentre le guardi.
/// Default: false — con l'aggiornamento acceso la pagina cambia sotto gli occhi
/// mentre si legge, quindi va acceso solo quando serve davvero.
///
public bool CatalogAutoRefresh { get; set; } = false;
// ═══════════════════════════════════════════════════════════════════
// AGGIUNTA AUTOMATICA DEI PRODOTTI SEGUITI
// ═══════════════════════════════════════════════════════════════════
// Bidoo rimette all'asta lo stesso articolo di continuo: seguendo il prodotto
// (stellina in Esplora) le aste nuove entrano nel monitor da sole.
/// Attiva la scansione periodica del catalogo per i prodotti seguiti.
public bool AutoAddProductsEnabled { get; set; } = true;
/// Ogni quanti secondi cercare aste nuove dei prodotti seguiti. Default: 90.
public int AutoAddScanSeconds { get; set; } = 90;
///
/// Quante aste percorrere nella scansione dei prodotti seguiti.
///
/// È un valore a parte da quello del catalogo, e più alto, per una
/// ragione precisa: il listato è ordinato per scadenza, quindi le aste che devono
/// ancora cominciare stanno in fondo. Con il tetto del catalogo (500) un'asta che
/// apre fra tre ore resta invisibile finché non si avvicina — ed è esattamente
/// quella che si vorrebbe avere nel monitor prima che parta.
///
/// Predefinito 2500: una cinquantina di richieste per scansione, che con la
/// cadenza predefinita sono meno di una al secondo.
///
public int AutoAddScanMaxAuctions { get; set; } = 2500;
///
/// Stato con cui entrano le aste aggiunte automaticamente.
/// Valori: "Stopped", "Watch" (predefinito), "Active".
///
/// Il valore predefinito è deliberatamente Osserva: un'asta che entra da sola
/// e comincia subito a puntare spenderebbe puntate vere senza che tu l'abbia vista.
///
public string AutoAddNewAuctionState { get; set; } = "Watch";
///
/// Tetto di aste aggiunte automaticamente presenti insieme nel monitor
/// (0 = nessun limite). Default: 20.
///
public int AutoAddMaxAuctions { get; set; } = 20;
///
/// Orizzonte temporale della sorveglianza: un'asta che comincia fra più di
/// tanti minuti non entra da sola nel monitor. 0 = nessun limite.
///
/// La ricerca scende in fondo al listato apposta per trovare le aste
/// programmate, ma "programmata" su Bidoo vuol dire anche fra dodici ore: senza un
/// orizzonte il monitor si riempie di aste che non succederanno per mezza giornata,
/// consumando il tetto di e nascondendo quelle
/// vicine. A mano si può sempre aggiungere qualunque asta: questo limite vale solo
/// per l'aggiunta automatica.
///
/// Predefinito 60 minuti. Un'asta in corso ha per costruzione un timer di
/// pochi secondi, quindi non è mai toccata dal filtro.
///
public int AutoAddMaxStartMinutes { get; set; } = 60;
///
/// Aggiungi da solo solo le aste che non sono ancora cominciate, cioè quelle
/// su cui nessuno ha ancora puntato.
///
/// Un'asta presa a metà si può seguire, ma la sua storia è già scritta per
/// buona parte: non si sa quanto è durata davvero, chi c'era prima, come è salito il
/// prezzo. Le statistiche che ne escono sono monche, e i dossier — che sono il
/// materiale su cui si tarano le strategie — raccontano solo la seconda metà.
/// Accendendo questa opzione entrano nel monitor soltanto le aste che si potranno
/// seguire dal primo istante all'ultimo.
///
/// Il prezzo da pagare è che si perdono le occasioni già in corso. Predefinito
/// false: chi vuole puntare, e non solo misurare, di solito le vuole entrambe.
///
public bool AutoAddOnlyNotStarted { get; set; } = false;
// RISCATTO AUTOMATICO DELLE PUNTATE GRATIS
// Bidoo lascia le ricompense dei giorni scorsi in attesa sulla pagina dedicata:
// un controllo periodico le raccoglie senza doverci pensare.
/// Attiva il controllo periodico delle ricompense da riscuotere.
public bool FreeBidsAutoClaimEnabled { get; set; } = true;
///
/// Ogni quanti minuti controllare. Il minimo applicato è 5: le ricompense sono
/// giornaliere, e interrogare il sito più spesso sarebbe traffico sprecato.
/// Default: 30.
///
public int FreeBidsCheckMinutes { get; set; } = 30;
// LIMITI LOG
///
/// Numero massimo di righe di log da mantenere per ogni singola asta (default: 500)
///
public int MaxLogLinesPerAuction { get; set; } = 500;
///
/// Numero massimo di righe di log da mantenere nel log globale (default: 1000)
///
public int MaxGlobalLogLines { get; set; } = 1000;
// NUOVE IMPOSTAZIONI STATO INIZIALE ASTE
///
/// Determina se all'apertura dell'applicazione le aste salvate devono essere avviate automaticamente.
/// Valori: "Active" = avvia, "Paused" = in pausa, "Stopped" = fermate (default)
///
public string DefaultStartAuctionsOnLoad { get; set; } = "Stopped";
///
/// Determina lo stato iniziale di una nuova asta quando viene aggiunta.
/// Valori: "Active" = attiva, "Paused" = in pausa, "Stopped" = fermata (default)
///
public string DefaultNewAuctionState { get; set; } = "Stopped";
///
/// Se TRUE, salva e ripristina lo stato effettivo (attiva/pausa/ferma) di ogni asta.
/// Se FALSE, applica DefaultStartAuctionsOnLoad a tutte le aste al caricamento.
/// Default: false (usa DefaultStartAuctionsOnLoad)
///
public bool RememberAuctionStates { get; set; } = false;
// ? NUOVO: LIMITE MINIMO PUNTATE
///
/// Numero minimo di puntate residue da mantenere sull'account.
/// Se impostato > 0, il sistema non punterà se le puntate residue scenderebbero sotto questa soglia.
/// Default: 0 (nessun limite)
///
public int MinimumRemainingBids { get; set; } = 0;
// ?? NUOVO: LIMITE STORIA PUNTATE
///
/// Numero massimo di puntate da visualizzare nella scheda "Storia Puntate".
/// Se impostato > 0, mostra solo le ultime N puntate dall'API.
/// Default: 20 (ultime 20 puntate)
///
public int MaxBidHistoryEntries { get; set; } = 20;
// ?? NUOVO: LIVELLO MINIMO LOG
///
/// Livello minimo di log da visualizzare.
/// Valori: "ErrorOnly" (solo errori), "Normal" (errori e warning),
/// "Informational" (info standard), "Debug" (dettagli sviluppo), "Trace" (tutto).
/// Default: "Normal" (uso giornaliero - errori e warning)
///
public string MinLogLevel { get; set; } = "Normal";
// ???????????????????????????????????????????????????????????????
// IMPOSTAZIONI DATABASE
// ???????????????????????????????????????????????????????????????
///
/// Abilita il salvataggio automatico delle aste completate nel database.
/// Default: true (consigliato per statistiche)
///
public bool DatabaseAutoSaveEnabled { get; set; } = true;
///
/// Esegue pulizia automatica duplicati all'avvio dell'applicazione.
/// Default: true (consigliato per mantenere database pulito)
///
public bool DatabaseAutoCleanupDuplicates { get; set; } = true;
///
/// Esegue pulizia automatica record incompleti all'avvio.
/// Default: false (può rimuovere dati utili in caso di errori temporanei)
///
public bool DatabaseAutoCleanupIncomplete { get; set; } = false;
///
/// Numero massimo di giorni da mantenere nei risultati aste.
/// Record più vecchi vengono eliminati automaticamente.
/// Default: 180 (6 mesi), 0 = disabilitato
///
public int DatabaseMaxRetentionDays { get; set; } = 180;
// ???????????????????????????????????????????????????????????????
// STRATEGIE AVANZATE DI PUNTATA
// ???????????????????????????????????????????????????????????????
// ❌ RIMOSSO: Jitter, Offset Dinamico, Latenza Adattiva
// Il timing è gestito SOLO da DefaultBidBeforeDeadlineMs
// Le strategie decidono SE puntare, non QUANDO
// 🎯 LOGGING GRANULARE
// ???????????????????????????????????????????????????????????????
///
/// Log quando viene piazzata una puntata [BID]
/// Default: true
///
public bool LogBids { get; set; } = true;
///
/// Log quando una strategia blocca la puntata [STRATEGY]
/// Default: true
///
public bool LogStrategyDecisions { get; set; } = true;
///
/// Log calcoli valore prodotto [VALUE]
/// Default: false (attiva per debug)
///
public bool LogValueCalculations { get; set; } = false;
///
/// Log rilevamento competizione e heat [COMPETITION]
/// Default: false
///
public bool LogCompetition { get; set; } = false;
///
/// Log timing e polling (molto verbose!) [TIMING]
/// Default: false (attiva solo per debug timing)
///
public bool LogTiming { get; set; } = false;
///
/// Log errori e warning [ERROR/WARN]
/// Default: true
///
public bool LogErrors { get; set; } = true;
///
/// Applica automaticamente i limiti salvati nel prodotto quando si aggiunge una nuova asta.
/// Se TRUE e il prodotto ha valori di default salvati, li applica automaticamente.
/// Default: true (consigliato per coerenza)
///
public bool AutoApplyProductDefaults { get; set; } = true;
///
/// Scelta priorità limiti quando si aggiunge un'asta per un prodotto già salvato:
/// - "ProductStats": Usa i limiti personalizzati salvati nelle statistiche prodotto (UserDefaultMinPrice, ecc.)
/// - "GlobalDefaults": Usa sempre i limiti globali (DefaultMinPrice, DefaultMaxPrice, ecc.)
/// Default: "ProductStats" (consigliato per usare limiti specifici per prodotto)
///
public string NewAuctionLimitsPriority { get; set; } = "ProductStats";
///
/// Log stato asta (terminata, reset, ecc.) [STATUS]
/// Default: true
///
public bool LogAuctionStatus { get; set; } = true;
///
/// Log profiling avversari [OPPONENT]
/// Default: false
///
public bool LogOpponentProfiling { get; set; } = false;
// 🎯 STRATEGIE SEMPLIFICATE
///
/// Entry Point: Usato SOLO per calcolare i limiti consigliati (70% del MaxPrice storico).
/// NON blocca le puntate! I limiti MinPrice/MaxPrice impostati dall'utente sono RIGIDI.
/// Default: true (per calcolo limiti consigliati)
///
public bool EntryPointEnabled { get; set; } = true;
///
/// Anti-bot: rinuncia al ciclo quando l'ultimo puntatore punta a cadenza fissa.
///
/// Predefinito cambiato a false. Le marche temporali di Bidoo sono in
/// secondi interi, quindi la regola non puo' davvero distinguere un automatismo da
/// una persona regolare: si riduce a "le ultime pause dell'avversario sono identiche
/// al secondo", cosa comunissima. Rigiocando i dossier con backtest.ps1
/// rifiutava fra il 4% e il 9% delle puntate, a seconda dell'anticipo, e la
/// premessa era comunque rovesciata: un avversario a cadenza fissa punta con secondi
/// di anticipo, quindi e' il piu' facile da battere aspettando l'ultimo istante.
///
/// Per rivedere il confronto con i propri dati:
/// $env:AUTOBIDDER_BACKTEST_LEGACY=1 prima di backtest.ps1.
///
public bool AntiBotDetectionEnabled { get; set; } = false;
///
/// User Exhaustion: Sfrutta utenti stanchi (oltre 50 puntate)
/// quando ci sono pochi altri bidder attivi.
/// Default: true
///
public bool UserExhaustionEnabled { get; set; } = true;
// 🎯 CONTROLLO CONVENIENZA PRODOTTO
///
/// Abilita il controllo di convenienza basato sul valore del prodotto.
/// Se attivo, blocca le puntate quando il costo totale supera il prezzo "Compra Subito"
/// di una percentuale superiore a MinSavingsPercentage.
/// Default: true
///
public bool ValueCheckEnabled { get; set; } = true;
///
/// Percentuale minima di risparmio richiesta per continuare a puntare.
/// Valori negativi = tolleranza alla perdita.
/// Es: -5 = permetti fino al 5% di perdita rispetto al "Compra Subito"
/// 0 = blocca se costa uguale o più del "Compra Subito"
/// 10 = richiedi almeno 10% di risparmio
/// Default: -5 (permetti fino al 5% di perdita)
///
public double MinSavingsPercentage { get; set; } = -5.0;
///
/// Abilita il controllo anti-collisione hardcoded.
/// Se attivo, blocca le puntate quando ci sono 3+ bidder attivi negli ultimi 10 secondi.
/// ATTENZIONE: Questo controllo può far perdere aste competitive!
/// Default: false (DISABILITATO - non blocca mai)
///
public bool HardcodedAntiCollisionEnabled { get; set; } = false;
// ── Fascia oraria sospesa ────────────────────────────────────────
// Vedi BiddingHours per i numeri: alle 0 e alle 9 la stessa asta costa quasi il
// doppio che fra le 10 e le 13.
// ── Apprendimento ────────────────────────────────────────────────
// Vedi Ml/LearningService. Sempre acceso: impara da ogni asta chiusa.
///
/// Lasciar fermare una puntata al modello quando il valore atteso è negativo.
/// Spento, il modello parla nel registro ma non decide.
///
public bool LearningGateEnabled { get; set; } = true;
///
/// Aste apprese sotto le quali il modello non ferma niente. Un modello appena nato
/// direbbe cose a caso, e a caso fermerebbe le puntate. Predefinito 300.
///
public int LearningMinAuctions { get; set; } = 300;
///
/// Moltiplica il costo della puntata nel confronto col valore atteso: 1 è il
/// valore atteso puro, 2 pretende che una puntata renda il doppio del suo costo.
/// Più alto = più selettivo = meno puntate.
///
public double LearningEvMultiplier { get; set; } = 1.0;
///
/// Secondi per avvio dedicati a leggere i dossier non ancora appresi, in
/// sottofondo. La prima volta ne servono diversi avvii; poi resta solo l'asta
/// appena chiusa.
///
public int LearningBootstrapSecondsPerStart { get; set; } = 120;
// ── Rimozione automatica delle aste concluse ─────────────────────
// Vedi FinishedAuctionCleanup: si toglie di serie, si trattiene cio' su cui c'e'
// stato un esborso.
/// Togliere da sole dall'elenco le aste concluse. Acceso di serie.
public bool AutoRemoveFinished { get; set; } = true;
/// Trattenere le aste su cui hai puntato, per controllarle a mano.
public bool AutoRemoveKeepWithMyBids { get; set; } = true;
/// Trattenere le aste vinte: c'è da confermare l'acquisto su Bidoo.
public bool AutoRemoveKeepWon { get; set; } = true;
/// Trattenere le aste di cui non si è vista la fine: esito da controllare.
public bool AutoRemoveKeepUnclear { get; set; } = true;
///
/// Versione dello schema delle impostazioni, per le migrazioni una tantum.
///
/// Un file scritto da una versione precedente non ha questo campo e vale
/// quindi 0. Serve perché un predefinito nuovo non arriva a chi ha già un
/// file salvato: il valore su disco vince sempre, ed è giusto che sia così, tranne
/// quando il vecchio valore veniva da una misura che si è poi rivelata sbagliata.
/// Vedi .
///
public int SettingsSchemaVersion { get; set; }
///
/// Sospendere le puntate nella fascia indicata. Acceso di serie.
/// L'asta resta comunque Attiva e riprende da sola: vedi .
///
public bool QuietHoursEnabled { get; set; } = true;
/// Prima ora della fascia sospesa, inclusa.
public int QuietHoursStart { get; set; } = BiddingHours.DefaultStart;
/// Prima ora in cui si torna a puntare.
public int QuietHoursEnd { get; set; } = BiddingHours.DefaultEnd;
///
/// Ritirarsi quando dall'altra parte c'è un'autopuntata armata. Acceso di serie.
/// Vedi per il meccanismo e i numeri misurati.
///
public bool AutoBidDuelWithdrawEnabled { get; set; } = true;
///
/// Risposte automatiche di fila oltre le quali ci si ritira. Predefinito 5.
/// Più basso risparmia prima ma rischia di scambiare per macchina un avversario
/// umano regolare; più alto è prudente e costa puntate.
///
public int AutoBidDuelResponses { get; set; } = AutoBidDuel.RispostePerConcludere;
// 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥
// RILEVAMENTO COMPETIZIONE E HEAT METRIC
// 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥
///
/// Abilita rilevamento competizione e heat metric.
/// Conta bidder attivi e collisioni per determinare il "calore" dell'asta.
/// Default: true
///
public bool CompetitionDetectionEnabled { get; set; } = true;
///
/// Finestra temporale in secondi per contare bidder attivi.
/// Default: 30 (ultimi 30 secondi)
///
public int CompetitionWindowSeconds { get; set; } = 30;
///
/// Numero minimo di bidder attivi per considerare l'asta "affollata".
/// Se >= a questa soglia, applica logica di evitamento.
/// Default: 3
///
public int CompetitionThreshold { get; set; } = 3;
///
/// Abilita auto-pausa per aste troppo competitive.
/// Default: false (solo warning, non pausa automatica)
///
public bool AutoPauseHotAuctions { get; set; } = false;
///
/// Soglia heat metric per auto-pausa (0-100).
/// Default: 80 (pausa se heat >= 80%)
///
public int HeatThresholdForPause { get; set; } = 80;
// ???????????????????????????????????????????????????????????????
// SOFT RETREAT E COLLISION MANAGEMENT
// ???????????????????????????????????????????????????????????????
///
/// Abilita soft retreat automatico dopo N collisioni consecutive.
/// Default: true
///
public bool SoftRetreatEnabled { get; set; } = true;
///
/// Numero di collisioni consecutive per attivare soft retreat.
/// Default: 3
///
public int SoftRetreatAfterCollisions { get; set; } = 3;
///
/// Durata del ritiro, in secondi.
///
/// Ridotta da 30 a 12: il timer delle aste osservate si azzera ogni 8-12
/// secondi, quindi trenta secondi di pausa sono tre cicli interi — nella pratica
/// l'asta è persa. Dodici secondi bastano a spezzare una serie di collisioni
/// senza consegnare la partita.
///
public int SoftRetreatDurationSeconds { get; set; } = 12;
///
/// Blocca la puntata se il prezzo sale più in fretta di tanti euro al secondo.
/// 0 = controllo spento (predefinito).
///
/// Prima era una costante nel codice fissata a 0,10 €/s, cioè dieci puntate
/// al secondo: su oltre 40.000 valutazioni riprese dai dossier non è mai scattata,
/// e la velocità massima mai osservata è 0,016 €/s. Se lo si vuole usare davvero,
/// un valore sensato sta intorno a 0,01-0,02 €/s.
///
public double PriceVelocityBlockPerSecond { get; set; } = 0;
// ???????????????????????????????????????????????????????????????
// PROBABILISTIC BIDDING
// ???????????????????????????????????????????????????????????????
///
/// Abilita policy di puntata probabilistica.
/// Decide se puntare con probabilità p basata su competizione e ROI.
/// Default: false (richiede tuning)
///
public bool ProbabilisticBiddingEnabled { get; set; } = false;
///
/// Probabilità base di puntata (0.0 - 1.0).
/// Default: 0.8 (80%)
///
public double BaseBidProbability { get; set; } = 0.8;
///
/// Fattore di riduzione probabilità per ogni bidder attivo extra.
/// Default: 0.1 (riduce del 10% per ogni bidder oltre la soglia)
///
public double ProbabilityReductionPerBidder { get; set; } = 0.1;
// ???????????????????????????????????????????????????????????????
// OPPONENT PROFILING
// ???????????????????????????????????????????????????????????????
///
/// Abilita profiling degli avversari.
/// Identifica utenti aggressivi e applica regole specifiche.
/// Default: true
///
public bool OpponentProfilingEnabled { get; set; } = true;
///
/// Soglia puntate per considerare un utente "aggressivo".
/// Default: 10 (se un utente ha fatto >= 10 puntate in un'asta)
///
public int AggressiveBidderThreshold { get; set; } = 10;
///
/// Dimensione finestra scorrevole per analisi bidder aggressivi.
/// Analizza le ultime N puntate invece del conteggio totale.
/// Default: 30 (ultime 30 puntate)
///
public int AggressiveBidderWindowSize { get; set; } = 30;
///
/// Soglia percentuale per considerare un utente "aggressivo".
/// Se un utente ha più di X% delle puntate nella finestra, è aggressivo.
/// Default: 40 (40% delle puntate)
///
public double AggressiveBidderPercentageThreshold { get; set; } = 40.0;
///
/// Dimensione finestra per rilevamento situazioni di duello.
/// Default: 20 (ultime 20 puntate)
///
public int DuelDetectionWindowSize { get; set; } = 20;
///
/// Azione da intraprendere con bidder aggressivi.
/// "Avoid" = evita l'asta, "Compete" = continua normalmente, "Outbid" = punta più aggressivamente
/// Default: "Compete" (cambiato da Avoid per essere meno restrittivo)
///
public string AggressiveBidderAction { get; set; } = "Compete";
// ???????????????????????????????????????????????????????????????
// BANKROLL & SAFETY MANAGER
// ???????????????????????????????????????????????????????????????
///
/// Abilita gestione bankroll per limitare spese.
/// Default: true
///
public bool BankrollManagerEnabled { get; set; } = true;
///
/// Limite massimo puntate per sessione (0 = illimitato).
/// Default: 0
///
public int MaxBidsPerSession { get; set; } = 0;
///
/// Limite massimo puntate per singola asta (0 = illimitato).
/// Default: 0
///
public int MaxBidsPerAuction { get; set; } = 0;
///
/// Budget massimo giornaliero in euro (0 = illimitato).
/// Calcolato come: puntate usate × costo medio puntata.
/// Default: 0
///
public double DailyBudgetEuro { get; set; } = 0;
///
/// Costo medio di una puntata, in euro. Entra nel budget e nel tetto di prezzo
/// consigliato per ogni prodotto. Predefinito 0,20: è il valore che il motore usa
/// nei calcoli di convenienza durante l'asta, e averne due diversi voleva dire
/// vedere due conti diversi sullo stesso articolo.
///
public double AverageBidCostEuro { get; set; } = 0.20;
///
/// Quota delle chiusure passate che il prezzo massimo consigliato punta a coprire.
///
/// Alzarla vuol dire restare in gioco in più aste ma spingersi su prezzi più
/// alti; abbassarla vuol dire puntare solo sulle occasioni buone e lasciar perdere
/// le altre. Predefinito 85%: la sola moda — il prezzo a cui l'articolo si
/// aggiudica più spesso — coprirebbe fra il 15% e il 44% delle aste a seconda del
/// prodotto, cioè si resterebbe fuori dalla maggior parte delle occasioni.
///
/// Il consiglio resta comunque limitato dal budget: vedi
/// .
///
public double SuggestedPriceCoveragePercent { get; set; } = 85;
// ???????????????????????????????????????????????????????????????
// LOGGING AVANZATO
// ???????????????????????????????????????????????????????????????
///
/// Abilita logging avanzato con metriche dettagliate.
/// Include: collisioni, timer scaduto, latenza, heat metric.
/// Default: true
///
public bool AdvancedLoggingEnabled { get; set; } = true;
///
/// Salva metriche per ogni puntata nel database.
/// Default: true
///
public bool SaveBidMetricsToDatabase { get; set; } = true;
// ═══════════════════════════════════════════════════════════════════
// INTERFACCIA
// ═══════════════════════════════════════════════════════════════════
///
/// Abilita la modalità scura dell'interfaccia.
/// Default: true (modalità scura)
///
public bool DarkMode { get; set; } = true;
}
///
/// Impostazioni su file, con una copia in memoria.
///
/// è su un percorso caldissimo: ogni anello di polling e ogni giro
/// del cecchino la chiamano, quindi decine di volte al secondo per ogni asta seguita.
/// Per questo la lettura del caso comune è senza lucchetto: si legge un
/// riferimento volatile, che in .NET è atomico. Il lucchetto serve solo a chi ricarica
/// da disco, cioè una volta ogni due secondi al massimo.
///
public static class SettingsManager
{
// Le impostazioni stanno nella radice fissa: contengono il percorso degli altri
// dati, quindi non possono a loro volta dipendere da quel percorso.
private static string _folder => AppPaths.ConfigRoot;
private static string _file => AppPaths.SettingsFile;
private static readonly object _reloadLock = new();
private static volatile AppSettings? _cached;
/// Scadenza della copia in memoria, in tick UTC.
private static long _cacheExpiryTicks;
private const int CACHE_TTL_MS = 2000;
/// Schema corrente. Alzarlo fa girare una volta sola.
public const int SchemaCorrente = 1;
///
/// Porta avanti un file scritto da una versione precedente. Restituisce true se
/// qualcosa è cambiato, così chi chiama sa che va risalvato.
///
/// Perché serve. Il valore su disco vince sui predefiniti, ed è
/// giusto: sono scelte dell'utente. Ma due di quei valori non erano scelte, erano
/// conseguenze di misure sbagliate — il tetto di 40 richieste al secondo e
/// l'anticipo di 1000 ms erano stati tarati quando il tetto stesso accodava le
/// chiamate e gonfiava il ping a 444 ms. Senza migrazione, chi aggiorna si ritrova
/// le correzioni di oggi già annullate dal proprio file, e conclude che non
/// funzionano.
///
/// Si tocca solo chi ha ancora esattamente il vecchio valore. Chi lo aveva
/// già cambiato di suo se lo tiene: quella è una scelta, e non spetta a un
/// aggiornamento ribaltarla.
///
public static bool Migra(AppSettings s)
{
if (s == null || s.SettingsSchemaVersion >= SchemaCorrente) return false;
if (Math.Abs(s.MaxRequestsPerSecond - 40) < 0.001) s.MaxRequestsPerSecond = 0;
if (s.DefaultBidBeforeDeadlineMs == 1000) s.DefaultBidBeforeDeadlineMs = 500;
s.SettingsSchemaVersion = SchemaCorrente;
return true;
}
public static AppSettings Load()
{
// Percorso comune: nessun lucchetto, nessuna allocazione.
var cached = _cached;
if (cached != null && DateTime.UtcNow.Ticks < Interlocked.Read(ref _cacheExpiryTicks))
return cached;
lock (_reloadLock)
{
// Un altro thread potrebbe aver già ricaricato mentre aspettavamo.
cached = _cached;
if (cached != null && DateTime.UtcNow.Ticks < Interlocked.Read(ref _cacheExpiryTicks))
return cached;
AppSettings loaded;
var daRisalvare = false;
try
{
var esiste = File.Exists(_file);
loaded = esiste
? JsonSerializer.Deserialize(File.ReadAllText(_file)) ?? new AppSettings()
: new AppSettings();
// Un file nuovo nasce già allo schema corrente: la migrazione riguarda
// solo chi arriva da una versione precedente.
if (!esiste) loaded.SettingsSchemaVersion = SchemaCorrente;
else daRisalvare = Migra(loaded);
}
catch
{
// File illeggibile o corrotto: si tiene quello che c'era, o i predefiniti.
loaded = _cached ?? new AppSettings();
}
// Scritto qui e non dentro Save: Load è sul percorso caldo e non deve
// toccare il disco, ma una migrazione capita una volta sola nella vita
// del file, e lasciarla non salvata la farebbe ripetere a ogni avvio.
if (daRisalvare)
{
try { File.WriteAllText(_file, JsonSerializer.Serialize(loaded, new JsonSerializerOptions { WriteIndented = true })); }
catch { /* si riproverà al prossimo avvio */ }
}
_cached = loaded;
Interlocked.Exchange(ref _cacheExpiryTicks, DateTime.UtcNow.AddMilliseconds(CACHE_TTL_MS).Ticks);
return loaded;
}
}
public static void Save(AppSettings settings)
{
try
{
if (!Directory.Exists(_folder)) Directory.CreateDirectory(_folder);
var txt = JsonSerializer.Serialize(settings, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(_file, txt);
// Pubblica subito i nuovi valori: il motore li vede al giro successivo.
_cached = settings;
Interlocked.Exchange(ref _cacheExpiryTicks, DateTime.UtcNow.AddMilliseconds(CACHE_TTL_MS).Ticks);
}
catch { }
}
}
}