using Application.Abstractions.Crypto;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Infrastructure.Crypto;

public sealed class UpbitRestClient(HttpClient http) : IUpbitClient
{
    private static readonly JsonSerializerOptions _jsonOptions = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
        NumberHandling = JsonNumberHandling.AllowReadingFromString
    };

    // ─── Candle ───────────────────────────────────────────────
    // 초 단위
    public async Task<IReadOnlyList<UpbitCandle>> GetSecondCandlesAsync(string market, int count, CancellationToken ct = default)
    {
        return await FetchCandlesAsync($"candles/seconds?market={market}&count={count}", ct);
    }

    // 분 단위
    public async Task<IReadOnlyList<UpbitCandle>> GetMinuteCandlesAsync(string market, int unit, int count, CancellationToken ct = default)
    {
        return await FetchCandlesAsync($"candles/minutes/{unit}?market={market}&count={count}", ct);
    }

    // 일 단위
    public async Task<IReadOnlyList<UpbitCandle>> GetDayCandlesAsync(string market, int count, CancellationToken ct = default)
    {
        return await FetchCandlesAsync($"candles/days?market={market}&count={count}", ct);
    }

    // 주 단위
    public async Task<IReadOnlyList<UpbitCandle>> GetWeekCandlesAsync(string market, int count, CancellationToken ct = default)
    {
        return await FetchCandlesAsync($"candles/weeks?market={market}&count={count}", ct);
    }

    // 월 단위
    public async Task<IReadOnlyList<UpbitCandle>> GetMonthCandlesAsync(string market, int count, CancellationToken ct = default)
    {
        return await FetchCandlesAsync($"candles/months?market={market}&count={count}", ct);
    }

    // 연 단위
    public async Task<IReadOnlyList<UpbitCandle>> GetYearCandlesAsync(string market, int count, CancellationToken ct = default)
    {
        return await FetchCandlesAsync($"candles/years?market={market}&count={count}", ct);
    }

    // ─── Paires/Market ───────────────────────────────────────────────

    public async Task<IReadOnlyList<UpbitMarket>> GetMarketsAsync(CancellationToken ct = default)
    {
        var items = await http.GetFromJsonAsync<List<UpbitMarketDto>>("market/all?is_details=true", _jsonOptions, ct) ?? [];

        return [..items.Select(m => new UpbitMarket(
            m.Market,                                                             // 페어(거래쌍)의 코드
            m.KoreanName,                                                         // 가상자산의 한글명
            m.EnglishName,                                                        // 가상자산의 영문명
            new UpbitMarketEvent(                                                 // 종목 경보 정보
                m.MarketEvent?.Warning ?? false,                                  // 유의 종목 여부
                new UpbitMarketCaution(                                           // 주의 종목 여부
                    m.MarketEvent?.Caution?.PriceFluctuations ?? false,           // 가격 급등락 경보
                    m.MarketEvent?.Caution?.TradingVolumeSoaring ?? false,        // 거래량 급증 경보
                    m.MarketEvent?.Caution?.DepositAmountSoaring ?? false,        // 입금량 급증 경보
                    m.MarketEvent?.Caution?.GlobalPriceDifferences ?? false,      // 국내외 가격 차이 경보
                    m.MarketEvent?.Caution?.ConcentrationOfSmallAccounts ?? false // 소수 계정 집중 거래 경보
                )
            )
        ))];
    }

    // ─── Ticker ───────────────────────────────────────────────
    // 현재가
    public async Task<IReadOnlyList<UpbitTickerDetail>> GetTickersAsync(string[] markets, CancellationToken ct = default)
    {
        var query = string.Join(",", markets);
        var items = await http.GetFromJsonAsync<List<UpbitTickerDetailDto>>($"ticker?markets={query}", _jsonOptions, ct) ?? [];

        return [..items.Select(t => new UpbitTickerDetail(
            t.Market,             // 페어(거래쌍) 코드
            t.TradeDate,          // 체근 체결 일자(UTC)
            t.TradeTime,          // 체근 체결 시작 (UTC)
            t.TradeDateKst,       // 체근 체결 일자(KST)
            t.TradeTimeKst,       // 체근 체결 시각(KST)
            t.TradeTimestamp,     // 체결 시각의 밀리초단위 타임스탬프
            t.OpeningPrice,       // 해당 페어의 첫 거래 가격
            t.HighPrice,          // 해당 페어의 최고 거래 가격
            t.LowPrice,           // 해덩 페어의 최저 거래 가격
            t.TradePrice,         // 해당 페어의 현재 가격
            t.PrevClosingPrice,   // 해당 페어의 전일 종가
            t.Change,             // 가격 변동 상태(EVEN: 보합, RISE: 항승, FALL: 하락)
            t.ChangePrice,        // 전일 종가 대비 가격 변화
            t.ChangeRate,         // 전일 종가 대비 가격 변화율
            t.SignedChangePrice,  // 전일 종가 대비 가격 변화 (부호 있는)(RISE: +, FALL: -)
            t.SignedChangeRate,   // 전일 종가 대비 가격 변화율
            t.TradeVolume,        // 최근 거래 수량
            t.AccTradePrice,      // 누적 거래 금액(UTC)
            t.AccTradePrice24h,   // 24시간 누적 거래 금액
            t.AccTradeVolume,     // 누적 거래량(UTC)
            t.AccTradeVolume24h,  // 24시간 누적 거래량
            t.Highest52WeekPrice, // 52주 신고가
            t.Highest52WeekDate,  // 52주 신고가 달성일
            t.Lowest52WeekPrice,  // 52주 신저가
            t.Lowest52WeekDate,   // 52주 신저가 달성일
            t.Timestamp           // 현재가 정보가 반영된 시각의 시간(mm)
        ))];
    }

    // ─── Trade ────────────────────────────────────────────────

    public async Task<IReadOnlyList<UpbitTrade>> GetTradesAsync(string market, int count, CancellationToken ct = default)
    {
        var items = await http.GetFromJsonAsync<List<UpbitTradeDto>>($"trades/ticks?market={market}&count={count}", _jsonOptions, ct) ?? [];

        return [..items.Select(t => new UpbitTrade(
            t.Market,           // 페어(거래쌍)의 코드
            t.TradeDateUtc,     // 체결 일자(UTC)
            t.TradeTimeUtc,     // 체결 시각(UTC)
            t.Timestamp,        // 체결 시간(mm)
            t.TradePrice,       // 최근 체결 가격
            t.TradeVolume,      // 최근 체결량
            t.PrevClosingPrice, // 전일 종가
            t.ChangePrice,      // 전일 종가 대비 가격 변화
            t.AskBid,           // 매수/매도 구분(BID: 매수, ASK: 매도)
            t.SequentialId      // 체결 번호(Unique)
        ))];
    }

    // ─── Orderbook ────────────────────────────────────────────

    public async Task<IReadOnlyList<UpbitOrderbook>> GetOrderbookAsync(string[] markets, CancellationToken ct = default)
    {
        var query = string.Join(",", markets);
        var items = await http.GetFromJsonAsync<List<UpbitOrderbookDto>>($"orderbook?markets={query}", _jsonOptions, ct) ?? [];

        return [..items.Select(o => new UpbitOrderbook(
            o.Market,                                               // 페어(거래쌍)의 코드
            o.TotalAskSize,                                         // 현재 호가의 전체 매도 잔량 합계
            o.TotalBidSize,                                         // 현재 호가의 전체 매수 잔량 합계
            [..o.OrderbookUnits.Select(u => new UpbitOrderbookUnit( // 호가 정보가 담긴 목록 (총 30호)
                u.AskPrice,                                         // 매도 호가
                u.BidPrice,                                         // 매수 호가
                u.AskSize,                                          // 매도 잔량
                u.BidSize                                           // 매수 잔량
            ))],
            o.Timestamp,                                            // 조회 요청 시간(ms)
            o.Level                                                 // 호가가 적용된 가격 단위
        ))];
    }

    // ─── Private helpers ──────────────────────────────────────
    // 캔들 정보 조회
    private async Task<IReadOnlyList<UpbitCandle>> FetchCandlesAsync(string url, CancellationToken ct)
    {
        var items = await http.GetFromJsonAsync<List<UpbitCandleDto>>(url, _jsonOptions, ct) ?? [];

        return [..items.Select(c => new UpbitCandle(
            c.Market,                // 페어(거래쌍)의 코드
            c.CandleDateTimeUtc,     // 캔들 기준 시각 (UTC)
            c.CandleDateTimeKst,     // 캔들 기준 시각 (KST)
            c.OpeningPrice,          // 시가
            c.HighPrice,             // 고가
            c.LowPrice,              // 저가
            c.TradePrice,            // 종가 (현재가)
            c.Timestamp,             // 마지막 틱이 저장된 시각 (ms)
            c.CandleAccTradePrice,   // 누적 거래 금액
            c.CandleAccTradeVolume,  // 누적 거래량
            c.Unit,                  // 캔들 집계 시간 단위 (분)
            c.PrevClosingPrice,      // 전일 종가 (UTC 0시 기준)
            c.ChangePrice,           // 전일 종가 대비 가격 변화
            c.ChangeRate,            // 전일 종가 대비 가격 변화율
            c.ConvertedTradePrice,   // 종가 환산 화폐 단위로 환산된 가격
            c.FirstDayOfPeriod       // 캔들 집계 시작일자
        ))];
    }

    // ─── Internal DTOs ────────────────────────────────────────

    private sealed class UpbitCandleDto
    {
        public string Market { get; set; } = default!;
        public string CandleDateTimeUtc { get; set; } = default!;
        public string CandleDateTimeKst { get; set; } = default!;
        public decimal OpeningPrice { get; set; }
        public decimal HighPrice { get; set; }
        public decimal LowPrice { get; set; }
        public decimal TradePrice { get; set; }
        public long Timestamp { get; set; }
        public decimal CandleAccTradePrice { get; set; }
        public decimal CandleAccTradeVolume { get; set; }
        public int? Unit { get; set; }
        public decimal? PrevClosingPrice { get; set; }
        public decimal? ChangePrice { get; set; }
        public decimal? ChangeRate { get; set; }
        public decimal? ConvertedTradePrice { get; set; }
        public string? FirstDayOfPeriod { get; set; }
    }

    private sealed class UpbitMarketDto
    {
        public string Market { get; set; } = default!;
        public string KoreanName { get; set; } = default!;
        public string EnglishName { get; set; } = default!;
        public UpbitMarketEventDto? MarketEvent { get; set; }
    }

    private sealed class UpbitMarketEventDto
    {
        public bool Warning { get; set; }
        public UpbitMarketCautionDto? Caution { get; set; }
    }

    private sealed class UpbitMarketCautionDto
    {
        public bool PriceFluctuations { get; set; }
        public bool TradingVolumeSoaring { get; set; }
        public bool DepositAmountSoaring { get; set; }
        public bool GlobalPriceDifferences { get; set; }
        public bool ConcentrationOfSmallAccounts { get; set; }
    }

    private sealed class UpbitTickerDetailDto
    {
        public string Market { get; set; } = default!;
        public string TradeDate { get; set; } = default!;
        public string TradeTime { get; set; } = default!;
        public string TradeDateKst { get; set; } = default!;
        public string TradeTimeKst { get; set; } = default!;
        public long TradeTimestamp { get; set; }
        public decimal OpeningPrice { get; set; }
        public decimal HighPrice { get; set; }
        public decimal LowPrice { get; set; }
        public decimal TradePrice { get; set; }
        public decimal PrevClosingPrice { get; set; }
        public string Change { get; set; } = default!;
        public decimal ChangePrice { get; set; }
        public decimal ChangeRate { get; set; }
        public decimal SignedChangePrice { get; set; }
        public decimal SignedChangeRate { get; set; }
        public decimal TradeVolume { get; set; }
        public decimal AccTradePrice { get; set; }
        public decimal AccTradePrice24h { get; set; }
        public decimal AccTradeVolume { get; set; }
        public decimal AccTradeVolume24h { get; set; }
        public decimal Highest52WeekPrice { get; set; }
        public string Highest52WeekDate { get; set; } = default!;
        public decimal Lowest52WeekPrice { get; set; }
        public string Lowest52WeekDate { get; set; } = default!;
        public long Timestamp { get; set; }
    }

    private sealed class UpbitTradeDto
    {
        public string Market { get; set; } = default!;
        public string TradeDateUtc { get; set; } = default!;
        public string TradeTimeUtc { get; set; } = default!;
        public long Timestamp { get; set; }
        public decimal TradePrice { get; set; }
        public decimal TradeVolume { get; set; }
        public decimal PrevClosingPrice { get; set; }
        public decimal ChangePrice { get; set; }
        public string AskBid { get; set; } = default!;
        public long SequentialId { get; set; }
    }

    private sealed class UpbitOrderbookDto
    {
        public string Market { get; set; } = default!;
        public decimal TotalAskSize { get; set; }
        public decimal TotalBidSize { get; set; }
        public List<UpbitOrderbookUnitDto> OrderbookUnits { get; set; } = [];
        public long Timestamp { get; set; }
        public decimal Level { get; set; }
    }

    private sealed class UpbitOrderbookUnitDto
    {
        public decimal AskPrice { get; set; }
        public decimal BidPrice { get; set; }
        public decimal AskSize { get; set; }
        public decimal BidSize { get; set; }
    }
}