"""Data normalization, cleaning, and transformation utilities."""

from __future__ import annotations

import logging
from typing import List, Optional

from core.trend_analysis import Candle

logger = logging.getLogger(__name__)


def normalize_candle(raw: dict) -> Optional[Candle]:
    """
    Normalize a raw database row into a Candle object.

    Handles missing fields, zero volumes, and invalid price data.

    Args:
        raw: Raw dict from database query

    Returns:
        Candle or None if data is invalid
    """
    try:
        open_price = float(raw.get("Open", 0))
        high_price = float(raw.get("High", 0))
        low_price = float(raw.get("Low", 0))
        close_price = float(raw.get("Close", 0))
        volume = float(raw.get("Volume", 0))
        timestamp = int(raw.get("timestamp", 0))
    except (TypeError, ValueError) as e:
        logger.warning(f"Failed to normalize candle: {e}")
        return None

    # Validate price data
    if any(v <= 0 for v in [open_price, high_price, low_price, close_price]):
        logger.warning(f"Invalid prices in candle: {raw}")
        return None

    if high_price < low_price:
        logger.warning(f"High < Low in candle: {raw}")
        return None

    if high_price < open_price and high_price < close_price:
        logger.warning(f"High below open/close in candle: {raw}")
        return None

    if low_price > open_price and low_price > close_price:
        logger.warning(f"Low above open/close in candle: {raw}")
        return None

    return Candle(
        timestamp=timestamp,
        open=open_price,
        high=high_price,
        low=low_price,
        close=close_price,
        volume=volume,
    )


def normalize_candles(raw_data: List[dict]) -> List[Candle]:
    """
    Normalize a list of raw database rows into Candle objects.

    Filters out invalid candles and sorts by timestamp ascending.

    Args:
        raw_data: List of raw dicts from database queries

    Returns:
        Sorted list of valid Candle objects
    """
    candles = []
    for row in raw_data:
        candle = normalize_candle(row)
        if candle is not None:
            candles.append(candle)

    # Sort by timestamp ascending (oldest first) for analysis
    candles.sort(key=lambda c: c.timestamp)
    return candles


def remove_duplicates(candles: List[Candle]) -> List[Candle]:
    """
    Remove duplicate candles based on timestamp, keeping the last one.

    Args:
        candles: List of candles (assumed sorted by timestamp)

    Returns:
        Deduplicated list of candles
    """
    if not candles:
        return []

    seen = {}
    for candle in candles:
        seen[candle.timestamp] = candle

    result = sorted(seen.values(), key=lambda c: c.timestamp)

    removed = len(candles) - len(result)
    if removed > 0:
        logger.info(f"Removed {removed} duplicate candles")

    return result


def fill_missing_candles(candles: List[Candle],
                          expected_interval: int = 3600,
                          max_gap: int = 3) -> List[Candle]:
    """
    Identify and optionally fill gaps in candle data.

    Does not interpolate prices (that would be misleading for backtesting).
    Logs gaps for awareness.

    Args:
        candles: List of candles sorted by timestamp
        expected_interval: Expected seconds between candles (e.g., 3600 for H1)
        max_gap: Maximum number of consecutive missing candles to log before warning

    Returns:
        Same candle list (gaps are logged but not filled to avoid misleading data)
    """
    if len(candles) < 2:
        return candles

    gaps = []
    for i in range(1, len(candles)):
        diff = candles[i].timestamp - candles[i - 1].timestamp
        if diff > expected_interval:
            missing = diff // expected_interval - 1
            gaps.append({
                'from': candles[i - 1].timestamp,
                'to': candles[i].timestamp,
                'missing': missing,
            })

    if gaps:
        total_missing = sum(g['missing'] for g in gaps)
        if total_missing > max_gap:
            logger.warning(f"Significant data gaps detected: {total_missing} missing candles across {len(gaps)} gaps")
        else:
            logger.info(f"Minor data gaps: {total_missing} missing candles")

    return candles


def validate_price_range(candles: List[Candle],
                          min_price: float = 0.01,
                          max_price: float = 1000000.0) -> List[Candle]:
    """
    Filter candles outside a reasonable price range.

    Args:
        candles: List of candles
        min_price: Minimum valid price
        max_price: Maximum valid price

    Returns:
        Filtered list of candles
    """
    filtered = [c for c in candles if min_price <= c.close <= max_price]

    removed = len(candles) - len(filtered)
    if removed > 0:
        logger.warning(f"Removed {removed} candles outside price range [{min_price}, {max_price}]")

    return filtered


def resample_candles(candles: List[Candle],
                      source_interval: int,
                      target_interval: int) -> List[Candle]:
    """
    Resample candles from one timeframe to another (e.g., M5 -> H1).

    Uses OHLCV aggregation. New candles are built from target_interval blocks.

    Args:
        candles: Source candles sorted by timestamp
        source_interval: Source interval in seconds
        target_interval: Target interval in seconds

    Returns:
        Resampled candles
    """
    if not candles:
        return []

    if target_interval <= source_interval:
        logger.warning("Target interval must be larger than source interval")
        return candles

    ratio = target_interval // source_interval
    if ratio < 2:
        return candles

    resampled = []
    for i in range(0, len(candles) - ratio + 1, ratio):
        block = candles[i:i + ratio]
        if len(block) < ratio:
            break

        resampled.append(Candle(
            timestamp=block[0].timestamp,
            open=block[0].open,
            high=max(c.high for c in block),
            low=min(c.low for c in block),
            close=block[-1].close,
            volume=sum(c.volume for c in block),
        ))

    return resampled