from __future__ import annotations

import argparse
from pathlib import Path
from typing import List

import numpy as np
import pandas as pd


TARGET_VARS = ["EQ", "SSQ", "REQ", "AT", "NSE", "TOP", "SEI", "PFV"]


def load_table(file_path: Path) -> pd.DataFrame:
    if not file_path.exists():
        raise FileNotFoundError(f"File not found: {file_path}")
    suffix = file_path.suffix.lower()
    if suffix == ".xlsx":
        return pd.read_excel(file_path)
    elif suffix == ".csv":
        return pd.read_csv(file_path)
    else:
        raise ValueError("Unsupported input format. Please use .xlsx or .csv.")


def save_table(df: pd.DataFrame, file_path: Path) -> None:
    file_path.parent.mkdir(parents=True, exist_ok=True)
    suffix = file_path.suffix.lower()
    if suffix == ".xlsx":
        df.to_excel(file_path, index=False)
    elif suffix == ".csv":
        df.to_csv(file_path, index=False, encoding="utf-8-sig")
    else:
        raise ValueError("Unsupported output format. Please use .xlsx or .csv.")


def prepare_input_data(df: pd.DataFrame) -> pd.DataFrame:
    df = df.copy()
    required_cols = ["destination_name"] + TARGET_VARS
    missing_cols = [c for c in required_cols if c not in df.columns]
    if missing_cols:
        raise ValueError(f"Missing required columns after preparation: {missing_cols}")

    df = df[required_cols].copy()
    for col in TARGET_VARS:
        df[col] = pd.to_numeric(df[col], errors="coerce")
    return df.dropna(subset=TARGET_VARS).reset_index(drop=True)


def build_anchor_table(df: pd.DataFrame, variables: List[str]) -> pd.DataFrame:
    records = []
    for var in variables:
        series = pd.to_numeric(df[var], errors="coerce").dropna()
        full_out = series.quantile(0.25)
        crossover = series.quantile(0.50)
        full_in = series.quantile(0.75)

        records.append({
            "Variable": var,
            "Full membership": round(float(full_in), 4),
            "Crossover point": round(float(crossover), 4),
            "Full non-membership": round(float(full_out), 4),
            "Mean": round(float(series.mean()), 4),
            "SD": round(float(series.std()), 4),
            "Max": round(float(series.max()), 4),
            "Min": round(float(series.min()), 4),
        })

    return pd.DataFrame(records)


def calibrate_direct(
    x: pd.Series,
    full_in: float,
    crossover: float,
    full_out: float,
    upper_target: float = 0.95,
    lower_target: float = 0.05,
) -> pd.Series:
    x = pd.to_numeric(x, errors="coerce")
    if not (full_in > crossover > full_out):
        raise ValueError(
            f"Invalid anchors: full_in={full_in}, crossover={crossover}, full_out={full_out}"
        )

    logit_upper = np.log(upper_target / (1 - upper_target))
    logit_lower = np.log(lower_target / (1 - lower_target))
    k_upper = logit_upper / (full_in - crossover)
    k_lower = abs(logit_lower) / (crossover - full_out)

    calibrated = []
    for value in x:
        if pd.isna(value):
            calibrated.append(np.nan)
        elif value >= crossover:
            z = k_upper * (value - crossover)
            calibrated.append(1 / (1 + np.exp(-z)))
        else:
            z = k_lower * (value - crossover)
            calibrated.append(1 / (1 + np.exp(-z)))

    return pd.Series(calibrated, index=x.index)


def apply_fuzzy_calibration(df: pd.DataFrame, anchor_df: pd.DataFrame) -> pd.DataFrame:
    calibrated_df = df.copy()
    for _, row in anchor_df.iterrows():
        var = row["Variable"]
        calibrated_df[var] = calibrate_direct(
            calibrated_df[var],
            full_in=float(row["Full membership"]),
            crossover=float(row["Crossover point"]),
            full_out=float(row["Full non-membership"]),
        )
    return calibrated_df


def main() -> None:
    parser = argparse.ArgumentParser(description="Fuzzy-set calibration for ski destination analytical data.")
    parser.add_argument("--input_file", type=str, default="outputs/analytical_dataset_before_calibration.xlsx")
    parser.add_argument("--output_anchor_file", type=str, default="outputs/calibration_anchors.xlsx")
    parser.add_argument("--output_calibrated_file", type=str, default="outputs/calibrated_analytical_dataset.xlsx")
    args = parser.parse_args()

    raw_df = load_table(Path(args.input_file))
    prepared_df = prepare_input_data(raw_df)
    anchor_df = build_anchor_table(prepared_df, TARGET_VARS)
    calibrated_df = apply_fuzzy_calibration(prepared_df, anchor_df)

    save_table(anchor_df, Path(args.output_anchor_file))
    save_table(calibrated_df, Path(args.output_calibrated_file))

    print("Fuzzy-set calibration completed successfully.")
    print(f"Number of cases: {len(calibrated_df)}")


if __name__ == "__main__":
    main()
