from __future__ import annotations

import csv
import re
from pathlib import Path
from typing import Any, Dict, List, Optional

import openpyxl
import xlrd

from store_customer_lib.import_export.mapper import looks_like_grouped_headers, resolve_grouped_field
from store_customer_lib.import_export.normalizer import clean_text, is_blank


def parse_file(file_path: str) -> Dict[str, Any]:
    path = Path(file_path)
    suffix = path.suffix.lower()
    if suffix == ".csv":
        tables = [_grid_to_table(_read_csv_grid(path), title=path.stem)]
    elif suffix == ".xlsx":
        tables = _read_xlsx_tables(path)
    elif suffix == ".xls":
        tables = _read_xls_tables(path)
    elif suffix in (".md", ".markdown", ".txt"):
        tables = _read_markdown_tables(path)
    else:
        raise ValueError("暂不支持的文件类型: {}".format(suffix))

    filtered_tables = [t for t in tables if t and t.get("rows")]
    return {"source_file": str(path), "format": suffix.lstrip("."), "tables": filtered_tables}


def _read_csv_grid(path: Path) -> List[List[str]]:
    with path.open("r", encoding="utf-8-sig", newline="") as f:
        return [[clean_text(c) for c in row] for row in csv.reader(f)]


def _read_xlsx_tables(path: Path) -> List[Dict[str, Any]]:
    workbook = openpyxl.load_workbook(path, data_only=True)
    tables: List[Dict[str, Any]] = []
    for sheet in workbook.worksheets:
        grid = [
            [sheet.cell(r, c).value for c in range(1, sheet.max_column + 1)]
            for r in range(1, sheet.max_row + 1)
        ]
        for merged in sheet.merged_cells.ranges:
            value = grid[merged.min_row - 1][merged.min_col - 1]
            for ri in range(merged.min_row - 1, merged.max_row):
                for ci in range(merged.min_col - 1, merged.max_col):
                    grid[ri][ci] = value
        grid = [[clean_text(c) for c in row] for row in grid]
        table = _grid_to_table(grid, title=sheet.title)
        if table:
            tables.append(table)
    return tables


def _read_xls_tables(path: Path) -> List[Dict[str, Any]]:
    workbook = xlrd.open_workbook(path, formatting_info=True)
    tables: List[Dict[str, Any]] = []
    for index in range(workbook.nsheets):
        sheet = workbook.sheet_by_index(index)
        grid = [
            [sheet.cell_value(ri, ci) for ci in range(sheet.ncols)]
            for ri in range(sheet.nrows)
        ]
        for row_lo, row_hi, col_lo, col_hi in sheet.merged_cells:
            value = grid[row_lo][col_lo]
            for ri in range(row_lo, row_hi):
                for ci in range(col_lo, col_hi):
                    grid[ri][ci] = value
        grid = [[clean_text(c) for c in row] for row in grid]
        table = _grid_to_table(grid, title=sheet.name)
        if table:
            tables.append(table)
    return tables


def _read_markdown_tables(path: Path) -> List[Dict[str, Any]]:
    lines = path.read_text(encoding="utf-8").splitlines()
    blocks: List[List[List[str]]] = []
    current: List[List[str]] = []

    def flush() -> None:
        nonlocal current
        if current:
            blocks.append(current)
            current = []

    for line in lines:
        stripped = line.strip()
        if not stripped.startswith("|"):
            flush()
            continue
        cells = [clean_text(c) for c in stripped.strip("|").split("|")]
        if all(re.fullmatch(r"[-:\s]+", c or "-") for c in cells):
            continue
        current.append(cells)
    flush()
    tables = []
    for i, block in enumerate(blocks):
        table = _grid_to_table(block, title="table_{}".format(i + 1))
        if table:
            tables.append(table)
    return tables


def _grid_to_table(grid: List[List[str]], title: str) -> Optional[Dict[str, Any]]:
    rows = [_trim_row(row) for row in grid if any(not is_blank(c) for c in row)]
    if not rows:
        return None
    title_row = None
    if _looks_like_title_row(rows[0]):
        title_row = rows.pop(0)
    if not rows:
        return None
    headers = rows[0]
    data_start = 1
    if _has_second_header_row(rows):
        headers = _combine_headers(rows[0], rows[1])
        data_start = 2
    headers = _dedupe_headers(headers)
    if not any(headers):
        return None
    mode = "grouped_records" if looks_like_grouped_headers(headers) else "generic"
    parsed_rows = _rows_to_dicts(rows[data_start:], headers, forward_fill=(mode == "grouped_records"))
    return {
        "title": clean_text(title_row[0]) if title_row else title,
        "headers": headers,
        "rows": parsed_rows,
        "mode": mode,
    }


def _trim_row(row: List[str]) -> List[str]:
    cleaned = [clean_text(c) for c in row]
    while cleaned and is_blank(cleaned[-1]):
        cleaned.pop()
    return cleaned


def _looks_like_title_row(row: List[str]) -> bool:
    first = clean_text(row[0]) if row else ""
    if "客户档案" in first or "会员" in first:
        return True
    unnamed = sum(1 for c in row if "unnamed" in clean_text(c).lower())
    return unnamed >= max(2, len(row) // 3)


def _has_second_header_row(rows: List[List[str]]) -> bool:
    if len(rows) < 2:
        return False
    markers = {"姓名", "性别", "生日"}
    return sum(1 for c in rows[1] if clean_text(c) in markers) >= 2


def _combine_headers(main: List[str], sub: List[str]) -> List[str]:
    result: List[str] = []
    group = ""
    for i, m in enumerate(main):
        s = sub[i] if i < len(sub) else ""
        mt, st = clean_text(m), clean_text(s)
        if not is_blank(mt):
            group = mt
        elif group and st:
            mt = group
        if is_blank(mt):
            result.append(st)
        elif is_blank(st):
            result.append(mt)
        elif st == mt:
            result.append(mt)
        else:
            result.append("{}_{}".format(mt, st))
    return result


def _dedupe_headers(headers: List[str]) -> List[str]:
    seen: Dict[str, int] = {}
    result: List[str] = []
    for i, h in enumerate(headers):
        text = clean_text(h)
        if not text or text.lower().startswith("unnamed"):
            text = "column_{}".format(i + 1)
        count = seen.get(text, 0)
        seen[text] = count + 1
        result.append(text if count == 0 else "{}_{}".format(text, count + 1))
    return result


def _rows_to_dicts(rows: List[List[str]], headers: List[str], forward_fill: bool) -> List[Dict[str, str]]:
    output: List[Dict[str, str]] = []
    prev = {h: "" for h in headers}
    for raw in rows:
        if not raw:
            continue
        row: Dict[str, str] = {}
        for i, h in enumerate(headers):
            val = clean_text(raw[i]) if i < len(raw) else ""
            if forward_fill and is_blank(val):
                val = prev.get(h, "")
            row[h] = val
            if not is_blank(val):
                prev[h] = val
        if _is_meaningful_row(row):
            output.append(row)
    return output


def _is_meaningful_row(row: Dict[str, str]) -> bool:
    vals = [v for v in row.values() if not is_blank(v)]
    if not vals:
        return False
    if len(vals) == 1 and vals[0].isdigit():
        return False
    return True
