import pandas as pd
import re
import os
from antyx.utils.types import detect_var_type


def format_number(x):
    """Format float numbers with comma and 2 decimals."""
    return f"{x:,.2f}" if pd.notnull(x) else ""


def clean_table(html):
    """Remove the outer <table> tag generated by pandas (kept for compatibility if needed)."""
    html = re.sub(r"<table[^>]*>", "", html)
    html = html.replace("</table>", "")
    return html


def render_table_block(title, table_html):
    """Create the table block with its title."""
    return f"""
    <div class="lines-block">
        <h2 class="lines-title">{title}</h2>
        <div class="table-container">
            <table class="table-custom">
                {table_html}
            </table>
        </div>
    </div>
    """

def render_summary_block(
    df,
    file_name,
    total_records,
    loaded_records,
    num_vars,
    omitted_records=0,
    theme="light",
    file_path=None,
):
    """
    Premium summary block with invisible tooltips and visible values.
    """

    # ---------------------------------------------------------
    # 1. File size
    # ---------------------------------------------------------
    if file_path and os.path.exists(file_path):
        size_bytes = os.path.getsize(file_path)
    else:
        size_bytes = df.memory_usage(deep=True).sum()

    def format_size(n):
        for unit in ["B", "KB", "MB", "GB"]:
            if n < 1024:
                return f"{n:.2f} {unit}"
            n /= 1024
        return f"{n:.2f} TB"

    size_str = format_size(size_bytes)

    # ---------------------------------------------------------
    # 2. Duplicate detection
    # ---------------------------------------------------------
    dup_mask = df.duplicated()
    dup_count = dup_mask.sum()

    pct_omitted = (omitted_records / total_records * 100) if total_records else 0
    pct_dup = (dup_count / total_records * 100) if total_records else 0

    # ---------------------------------------------------------
    # 3. Missing values
    # ---------------------------------------------------------
    total_cells = df.shape[0] * df.shape[1]
    total_missing = df.isna().sum().sum()
    pct_missing = (total_missing / total_cells * 100) if total_cells else 0

    cols_with_missing = (df.isna().sum() > 0).sum()

    # ---------------------------------------------------------
    # 4. Variable types
    # ---------------------------------------------------------
    type_counts = {
        "Numeric": 0,
        "Binary": 0,
        "Categorical": 0,
        "Datetime": 0,
        "Other": 0,
    }

    high_cardinality = 0

    for col in df.columns:
        vtype = detect_var_type(df[col])

        if vtype == "numeric":
            type_counts["Numeric"] += 1
        elif vtype == "binary":
            type_counts["Binary"] += 1
        elif vtype == "categorical":
            type_counts["Categorical"] += 1
            if df[col].nunique(dropna=True) > 50:
                high_cardinality += 1
        elif vtype == "datetime":
            type_counts["Datetime"] += 1
        else:
            type_counts["Other"] += 1

    # ---------------------------------------------------------
    # 5. ML Readiness
    # ---------------------------------------------------------
    leakage_risk = any(
        col.lower() in ["target", "label", "outcome", "y", "class"]
        for col in df.columns
    )

    fe_complexity = (
        type_counts["Categorical"]
        + type_counts["Datetime"]
        + type_counts["Other"]
        + high_cardinality
    )

    # ---------------------------------------------------------
    # 6. Tooltip helper
    # ---------------------------------------------------------
    def tip(label, value, text, level=0):
        return f"""
        <p class="level-{level}">
            <span class="metric-label">{label}
                <span class="tooltip-text">{text}</span>
            </span>
            <span class="metric-value">{value}</span>
        </p>
        """

    # ---------------------------------------------------------
    # 7. Build cards
    # ---------------------------------------------------------
    data_structure_html = (
        tip("Total rows", total_records, "Total number of rows before omissions.")
        + tip("Loaded rows", loaded_records, "Rows successfully loaded into memory.")
        + tip("Omitted rows", f"{omitted_records} ({pct_omitted:.2f}%)", "Rows skipped due to read errors.")
        + tip("Duplicate rows", f"{dup_count} ({pct_dup:.2f}%)", "Rows identified as duplicates.")
        + tip("Missing cells", f"{pct_missing:.2f}%", "Percentage of missing values in the dataset.")
        + tip("Columns with missing", cols_with_missing, "Columns containing at least one missing value.")
    )

    feature_overview_html = (
            tip("Total features", num_vars, "Total number of columns.", level=0)
            + tip("Numeric", type_counts["Numeric"], "Numeric variables.", level=1)
            + tip("Categorical", type_counts["Categorical"], "Categorical variables.", level=1)
            + tip("Binary", type_counts["Binary"], "Binary variables.", level=1)
            + tip("Datetime", type_counts["Datetime"], "Datetime variables.", level=1)
            + tip("Other", type_counts["Other"], "Other variable types.", level=1)
            + tip("High cardinality", high_cardinality, "Categorical variables with >50 unique values.", level=0)
    )

    ml_readiness_html = (
        tip("Missing ratio", f"{pct_missing:.2f}%", "Global missing value ratio.")
        + tip("High cardinality risk", high_cardinality, "Number of high-cardinality features.")
        + tip("Memory footprint", size_str, "Estimated dataset memory usage.")
        + tip("Potential leakage risk", leakage_risk, "Heuristic detection of leakage risk.")
        + tip("Feature engineering complexity", fe_complexity, "Estimated complexity of feature engineering.")
    )

    # ---------------------------------------------------------
    # 8. Final HTML
    # ---------------------------------------------------------
    return f"""
        <div id="summary" class="summary-block theme-{theme}">
            <p><strong>File:</strong> {file_name}</p>
            <p><strong>Size:</strong> {size_str}</p>

            <div class="summary-row">

                <div class="summary-card">
                    <h3>Data Structure</h3>
                    <div class="indent">
                        {data_structure_html}
                    </div>
                </div>

                <div class="summary-card">
                    <h3>Feature Overview</h3>
                    <div class="indent">
                        {feature_overview_html}
                    </div>
                </div>

                <div class="summary-card">
                    <h3>ML Readiness</h3>
                    <div class="indent">
                        {ml_readiness_html}
                    </div>
                </div>

            </div>

            <div class="summary-note">
                <span>ℹ️</span>
                <span>Hover over any metric to see additional information.</span>
            </div>
        </div>
    """

def lines(
    df,
    file_name="",
    total_records=None,
    omitted_records=0,
    theme="light",
    file_path=None,
):
    """
    Generate HTML for the first and last 5 rows of the dataframe
    along with the summary block.
    """

    df_copy = df.copy()

    # 1. Format numeric columns (floats only)
    for col in df_copy.select_dtypes(include=["float64"]).columns:
        df_copy[col] = df_copy[col].apply(format_number)

    # 2. First and last 5 rows
    first_lines = df_copy.head(5)
    last_lines = df_copy.tail(5)

    # 3. Detect variable types and decide alignment classes
    col_align = {}  # column name -> CSS class
    for col in df.columns:
        vtype = detect_var_type(df[col])
        if vtype == "numeric":
            col_align[col] = "align-right"
        elif vtype == "datetime":
            col_align[col] = "align-right"
        elif vtype == "binary":
            col_align[col] = "align-center"
        else:
            col_align[col] = "align-left"

    def build_table_html(df_subset):
        """Build HTML for a table with aligned headers and cells by variable type."""
        columns = list(df_subset.columns)

        # Header row
        header_cells = "".join(
            f"<th class='{col_align.get(col, 'align-left')}'>{col}</th>"
            for col in columns
        )
        header_html = f"<thead><tr>{header_cells}</tr></thead>"

        # Body rows
        body_rows = []
        for _, row in df_subset.iterrows():
            row_cells = []
            for col in columns:
                cls = col_align.get(col, "align-left")
                value = row[col]
                row_cells.append(f"<td class='{cls}'>{value}</td>")
            body_rows.append(f"<tr>{''.join(row_cells)}</tr>")
        body_html = f"<tbody>{''.join(body_rows)}</tbody>"

        return header_html + body_html

    # 4. Tables HTML
    first_table_html = build_table_html(first_lines)
    last_table_html = build_table_html(last_lines)

    first_block = render_table_block("First lines data", first_table_html)
    last_block = render_table_block("Last lines data", last_table_html)

    # 5. Summary block (always shown, including in-memory DataFrames)
    effective_file_name = file_name if file_name else "In-memory DataFrame"
    effective_total_records = total_records if total_records is not None else df.shape[0]

    summary_block = render_summary_block(
        df=df,
        file_name=effective_file_name,
        total_records=effective_total_records,
        loaded_records=df.shape[0],
        num_vars=df.shape[1],
        omitted_records=omitted_records,
        theme=theme,
        file_path=file_path,
    )

    return summary_block + first_block + last_block