"""
Core trend analysis logic.
"""

from typing import Optional

import pandas as pd
import numpy as np
from datetime import timedelta
from scipy import stats

from onsight_trend.config import DEFAULT_MAGNITUDE_CATEGORIES

N_FIXED_SEGMENTS = 10
OVERLAP_FRACTION = 0.6

# Common parameter column names (case-insensitive)
PARAMETER_COL_NAMES = (
    "value", "parameter", "thp", "pressure", "tubing_head_pressure",
    "rate", "oil_rate", "water_rate", "gas_rate", "temperature",
    "flow_rate", "fbhp", "annulus", "choke",
)


def _to_dataframe(data) -> pd.DataFrame:
    """Convert Series or DataFrame to standard datetime + value DataFrame."""
    if isinstance(data, pd.Series):
        if isinstance(data.index, pd.DatetimeIndex):
            df = pd.DataFrame({"datetime": data.index, "value": data.values})
        else:
            raise ValueError(
                "Series must have DatetimeIndex. Use DataFrame with 'datetime' column or Series with DatetimeIndex."
            )
    elif isinstance(data, pd.DataFrame):
        df = data.copy()
        df.columns = df.columns.str.strip().str.lower()
        time_cols = [c for c in df.columns if c in ("time", "datetime", "date", "timestamp")]
        param_cols = [c for c in df.columns if c in PARAMETER_COL_NAMES]
        if not param_cols:
            numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
            param_cols = [c for c in numeric_cols if c not in time_cols]
        if not time_cols:
            raise ValueError(f"No time column found. Expected: time, datetime, date, timestamp. Got: {list(df.columns)}")
        if not param_cols:
            raise ValueError(f"No parameter column found. Expected one of: {PARAMETER_COL_NAMES}. Got: {list(df.columns)}")
        df = df.rename(columns={time_cols[0]: "datetime", param_cols[0]: "value"})[["datetime", "value"]]
    else:
        raise TypeError("data must be pandas Series (with DatetimeIndex) or DataFrame")

    df["datetime"] = pd.to_datetime(df["datetime"])
    df = df.sort_values("datetime").reset_index(drop=True)
    return df


def _get_data_for_duration(df: pd.DataFrame, duration_days: float) -> pd.DataFrame:
    if df.empty:
        return df
    end_time = df["datetime"].max()
    start_time = end_time - timedelta(days=duration_days)
    return df[df["datetime"] >= start_time].reset_index(drop=True)


def _rolling_segment_analysis(df: pd.DataFrame) -> list[dict]:
    if len(df) < 2:
        return []

    df = df.copy()
    df["hours"] = (df["datetime"] - df["datetime"].min()).dt.total_seconds() / 3600
    total_hours = df["hours"].max() - df["hours"].min()

    if total_hours <= 0:
        return []

    segment_hours = total_hours * OVERLAP_FRACTION
    step_hours = (total_hours - segment_hours) / (N_FIXED_SEGMENTS - 1) if N_FIXED_SEGMENTS > 1 else total_hours

    segments = []
    for k in range(N_FIXED_SEGMENTS):
        start_hour = k * step_hours
        end_hour = start_hour + segment_hours

        if end_hour > df["hours"].max():
            break

        seg_df = df[(df["hours"] >= start_hour) & (df["hours"] <= end_hour)]
        if len(seg_df) < 2:
            continue

        start_row = seg_df.iloc[0]
        end_row = seg_df.iloc[-1]
        sub_change = end_row["value"] - start_row["value"]

        slope, intercept, r_value, p_value, std_err = stats.linregress(
            seg_df["hours"].values, seg_df["value"].values
        )

        sub_direction = "increase" if sub_change > 0 else ("decrease" if sub_change < 0 else "steady")
        reg_direction = "increase" if slope > 0 else ("decrease" if slope < 0 else "steady")

        segments.append({
            "start_time": start_row["datetime"],
            "end_time": end_row["datetime"],
            "start_value": start_row["value"],
            "end_value": end_row["value"],
            "subtraction_change": sub_change,
            "regression_slope": slope,
            "sub_direction": sub_direction,
            "reg_direction": reg_direction,
        })

    return segments


def _vote_trend(segments: list[dict]) -> tuple[str, float]:
    if not segments:
        return "steady", 0.0

    sub_increase = sum(1 for s in segments if s["sub_direction"] == "increase")
    sub_decrease = sum(1 for s in segments if s["sub_direction"] == "decrease")
    reg_increase = sum(1 for s in segments if s["reg_direction"] == "increase")
    reg_decrease = sum(1 for s in segments if s["reg_direction"] == "decrease")

    total_increase = sub_increase + reg_increase
    total_decrease = sub_decrease + reg_decrease
    total_steady = len(segments) * 2 - total_increase - total_decrease

    total_votes = len(segments) * 2

    if total_increase > total_decrease and total_increase > total_steady:
        trend = "increase"
        winning_votes = total_increase
    elif total_decrease > total_increase and total_decrease > total_steady:
        trend = "decrease"
        winning_votes = total_decrease
    else:
        trend = "steady"
        winning_votes = total_steady

    confidence = round(100 * winning_votes / total_votes, 1) if total_votes > 0 else 0.0
    return trend, confidence


def _categorize_magnitude(change: float, magnitude_categories: dict) -> str:
    abs_change = abs(change)
    for cat, (lo, hi) in magnitude_categories.items():
        if lo <= abs_change < hi:
            return cat
    return list(magnitude_categories.keys())[0] if magnitude_categories else "negligible"


class TrendResult:
    """Result of trend analysis with direction, magnitude, and confidence."""

    def __init__(
        self,
        direction: str,
        magnitude: float,
        confidence: float,
        magnitude_pct: float = 0.0,
        magnitude_category: str = "negligible",
        start_value: float = 0.0,
        end_value: float = 0.0,
        duration_days: float = 0.0,
        n_segments: int = 0,
    ):
        self.direction = direction
        self.magnitude = magnitude
        self.confidence = confidence
        self.magnitude_pct = magnitude_pct
        self.magnitude_category = magnitude_category
        self.start_value = start_value
        self.end_value = end_value
        self.duration_days = duration_days
        self.n_segments = n_segments

    def __repr__(self):
        return f"TrendResult(direction={self.direction!r}, magnitude={self.magnitude}, confidence={self.confidence})"


def analyze_trend(
    data,
    duration_days: float = 7.0,
    magnitude_categories: Optional[dict] = None,
) -> TrendResult:
    """
    Analyze trend of a time-series parameter using rolling segmentation.

    Parameters
    ----------
    data : pandas.Series or pandas.DataFrame
        Time-series data. Series must have DatetimeIndex. DataFrame must have
        time column (datetime/time/date/timestamp) and parameter column
        (value, thp, pressure, rate, temperature, etc.).
    duration_days : float, default 7.0
        Analysis window in days (uses most recent data).
    magnitude_categories : dict, optional
        Custom magnitude thresholds: {name: (min, max)} in units of your parameter.
        See README for how to customize.

    Returns
    -------
    TrendResult
        Object with .direction, .magnitude, .confidence attributes.

    Example
    -------
    >>> from onsight_trend import analyze_trend
    >>> import pandas as pd
    >>> df = pd.read_csv("data.csv")
    >>> result = analyze_trend(df)
    >>> print(result.direction)
    >>> print(result.magnitude)
    >>> print(result.confidence)
    """
    if magnitude_categories is None:
        magnitude_categories = DEFAULT_MAGNITUDE_CATEGORIES.copy()

    df = _to_dataframe(data)
    df_window = _get_data_for_duration(df, duration_days)

    if len(df_window) < 2:
        return TrendResult(
            direction="steady",
            magnitude=0.0,
            confidence=0.0,
            magnitude_pct=0.0,
            magnitude_category="negligible",
            start_value=df_window.iloc[0]["value"] if len(df_window) == 1 else 0.0,
            end_value=df_window.iloc[-1]["value"] if len(df_window) == 1 else 0.0,
            duration_days=duration_days,
            n_segments=0,
        )

    segments = _rolling_segment_analysis(df_window)
    direction, confidence = _vote_trend(segments)

    start_value = df_window.iloc[0]["value"]
    end_value = df_window.iloc[-1]["value"]
    change = end_value - start_value
    change_pct = 100 * change / start_value if start_value != 0 else 0.0
    category = _categorize_magnitude(change, magnitude_categories)

    return TrendResult(
        direction=direction,
        magnitude=round(change, 2),
        confidence=confidence,
        magnitude_pct=round(change_pct, 2),
        magnitude_category=category,
        start_value=round(start_value, 2),
        end_value=round(end_value, 2),
        duration_days=duration_days,
        n_segments=len(segments),
    )
