"""
銀行明細 CSV 取込・照会モジュール
"""
import csv
import hashlib
import io
import uuid
from datetime import datetime, date
from typing import Optional, List

from fastapi import APIRouter, HTTPException, UploadFile, File, Form
from pydantic import BaseModel

from app.core.database import get_connection

router = APIRouter(prefix="/bank-statements", tags=["銀行明細"])

# ── CSV 列名検出パターン ──────────────────────────────────────

_DATE_KEYS    = ['日付', '取引日', '年月日', '取引年月日', 'date', '取引日付']
_DESC_KEYS    = ['摘要', '内容', '取引内容', '支払先', '摘要/メモ', '入出金先内容',
                 'description', '取引先', 'お取引内容', '取引詳細']
_DEBIT_KEYS   = ['お支払い金額', 'お支払金額', '支払額', '引出し', '出金', '引出額',
                 '借方', 'debit', '出金金額', '支払い金額', '引き出し金額']
_CREDIT_KEYS  = ['お預かり金額', '預入れ', '預入', '入金', '預入金額',
                 '貸方', 'credit', '入金金額', 'お預り金額', '預け入れ金額']
_BALANCE_KEYS = ['残高', '差引残高', 'balance', '帳簿残高', '取引後残高']
_MEMO_KEYS    = ['メモ', '備考', '通帳メモ', 'memo', '注記', '備考欄']


def _find_col(headers: List[str], patterns: List[str]) -> Optional[int]:
    lowers = [h.strip().lower() for h in headers]
    for pat in patterns:
        p = pat.lower()
        for i, h in enumerate(lowers):
            if p in h:
                return i
    return None


def _parse_amount(s: str) -> int:
    if not s:
        return 0
    cleaned = (s.strip()
               .replace(',', '').replace('，', '')
               .replace('¥', '').replace('￥', '')
               .replace(' ', '').replace('\u3000', ''))
    if cleaned in ('', '-', '―', '－', '▲', 'ー'):
        return 0
    try:
        return int(float(cleaned))
    except ValueError:
        return 0


def _parse_date(s: str) -> Optional[date]:
    s = (s.strip()
         .replace('/', '-').replace('.', '-')
         .replace('年', '-').replace('月', '-').replace('日', ''))
    for fmt in ['%Y-%m-%d', '%y-%m-%d']:
        try:
            return datetime.strptime(s, fmt).date()
        except ValueError:
            continue
    s2 = s.replace('-', '')
    if len(s2) == 8:
        try:
            return datetime.strptime(s2, '%Y%m%d').date()
        except ValueError:
            pass
    if len(s2) == 6:
        try:
            return datetime.strptime('20' + s2, '%Y%m%d').date()
        except ValueError:
            pass
    return None


def _detect_encoding(raw: bytes) -> str:
    for enc in ['utf-8-sig', 'utf-8', 'shift_jis', 'cp932', 'euc-jp']:
        try:
            raw.decode(enc)
            return enc
        except (UnicodeDecodeError, LookupError):
            continue
    return 'utf-8'


def _make_hash(tx_date: str, description: str, debit: int,
               credit: int, balance: Optional[int]) -> str:
    s = f"{tx_date}|{description}|{debit}|{credit}|{balance}"
    return hashlib.md5(s.encode()).hexdigest()


# ── 三菱UFJ MEISAI フォーマット対応 ──────────────────────────
# 行タイプ: "1"=口座ヘッダー, "2"=明細, "8"=フッター, "9"=合計
# 列: [タイプ, 日付, 摘要区分, 摘要詳細, 出金額, 入金額, 残高]

def _is_meisai_format(rows: List[List[str]]) -> bool:
    """三菱UFJ銀行 MEISAI フォーマット判定"""
    type1 = any(r and r[0] == '1' for r in rows)
    type2 = any(r and r[0] == '2' for r in rows)
    return type1 and type2


def _parse_meisai_rows(rows: List[List[str]]) -> tuple:
    """MEISAI フォーマットをパース。(明細リスト, 銀行名ヒント) を返す"""
    bank_hint = ''
    parsed = []

    for row in rows:
        if not row:
            continue
        row_type = row[0]

        if row_type == '1' and len(row) >= 3:
            # ヘッダー行: [1, 支店コード, 支店名, ..., 科目, 口座番号, 口座名, ...]
            branch = row[2].strip() if len(row) > 2 else ''
            bank_hint = f"三菱UFJ銀行 {branch}" if branch else '三菱UFJ銀行'

        elif row_type == '2' and len(row) >= 5:
            # 明細行: [2, 日付, 摘要区分, 摘要詳細, 出金額, 入金額, 残高]
            tx_date = _parse_date(row[1])
            if tx_date is None:
                continue

            summary1 = row[2].strip() if len(row) > 2 else ''
            summary2 = row[3].strip() if len(row) > 3 else ''
            desc = f"{summary1}　{summary2}".strip('　') if summary2 else summary1

            debit   = _parse_amount(row[4]) if len(row) > 4 else 0
            credit  = _parse_amount(row[5]) if len(row) > 5 else 0
            bal_str = row[6].strip() if len(row) > 6 else ''
            balance = _parse_amount(bal_str) if bal_str else None

            parsed.append({
                'tx_date':     tx_date.isoformat(),
                'description': desc,
                'debit':       debit,
                'credit':      credit,
                'balance':     balance,
                'memo':        '',
                'row_hash':    _make_hash(tx_date.isoformat(), desc, debit, credit, balance),
            })

    return parsed, bank_hint


def _parse_csv_bytes(content: bytes) -> tuple:
    """CSVをパース。(明細リスト, 銀行名ヒント) を返す"""
    enc = _detect_encoding(content)
    text = content.decode(enc).replace('\x00', '').strip()

    reader = csv.reader(io.StringIO(text))
    rows = list(reader)

    if not rows:
        raise HTTPException(status_code=400, detail="CSVファイルが空です")

    # MEISAI（三菱UFJ）フォーマット優先検出
    if _is_meisai_format(rows):
        return _parse_meisai_rows(rows)

    # ── 汎用ヘッダー行検出 ──
    header_row = 0
    for i, row in enumerate(rows[:15]):
        row_str = ' '.join(row).lower()
        if any(k.lower() in row_str for k in ['日付', '取引日', 'date', '年月日']):
            header_row = i
            break

    headers = rows[header_row]
    date_col    = _find_col(headers, _DATE_KEYS)
    desc_col    = _find_col(headers, _DESC_KEYS)
    debit_col   = _find_col(headers, _DEBIT_KEYS)
    credit_col  = _find_col(headers, _CREDIT_KEYS)
    balance_col = _find_col(headers, _BALANCE_KEYS)
    memo_col    = _find_col(headers, _MEMO_KEYS)

    if date_col is None:
        raise HTTPException(
            status_code=400,
            detail="日付列が見つかりません。CSVのヘッダー行に「日付」「取引日」等の列名が必要です。"
        )

    parsed = []
    for row in rows[header_row + 1:]:
        if not row or all(c.strip() == '' for c in row):
            continue
        if len(row) <= date_col:
            continue
        raw_date = row[date_col].strip()
        if not raw_date:
            continue
        tx_date = _parse_date(raw_date)
        if tx_date is None:
            continue

        def _safe(col: Optional[int]) -> str:
            return row[col].strip() if col is not None and len(row) > col else ''

        desc    = _safe(desc_col)
        debit   = _parse_amount(_safe(debit_col))
        credit  = _parse_amount(_safe(credit_col))
        bal_str = _safe(balance_col)
        balance = _parse_amount(bal_str) if bal_str else None
        memo    = _safe(memo_col)

        parsed.append({
            'tx_date':     tx_date.isoformat(),
            'description': desc,
            'debit':       debit,
            'credit':      credit,
            'balance':     balance,
            'memo':        memo,
            'row_hash':    _make_hash(tx_date.isoformat(), desc, debit, credit, balance),
        })

    return parsed, ''


# ── エンドポイント ─────────────────────────────────────────────

@router.post("/preview", summary="CSVプレビュー（重複チェック）")
async def preview_csv(
    file:      UploadFile = File(...),
    bank_name: str        = Form(""),
):
    """CSVを解析し、重複チェック結果付きでプレビューを返す（DBには保存しない）"""
    content = await file.read()
    if len(content) > 10_000_000:
        raise HTTPException(status_code=400, detail="ファイルサイズが大きすぎます（上限10MB）")

    rows, bank_hint = _parse_csv_bytes(content)
    if not rows:
        raise HTTPException(status_code=400, detail="有効なデータ行がありません")

    # 銀行名未入力の場合はCSVから自動検出した値を使用
    if not bank_name.strip() and bank_hint:
        bank_name = bank_hint

    # DB上の既存ハッシュを取得して重複チェック
    with get_connection() as conn, conn.cursor() as cur:
        cur.execute("SELECT row_hash FROM bank_statement_rows")
        existing_hashes = {r['row_hash'] for r in cur.fetchall()}

    result = []
    for row in rows:
        row['status'] = 'duplicate' if row['row_hash'] in existing_hashes else 'new'
        result.append(row)

    new_count = sum(1 for r in result if r['status'] == 'new')
    dup_count = sum(1 for r in result if r['status'] == 'duplicate')

    return {
        'total':      len(result),
        'new':        new_count,
        'duplicates': dup_count,
        'bank_name':  bank_name,
        'rows':       result,
    }


class ImportRow(BaseModel):
    tx_date:     str
    description: str
    debit:       int
    credit:      int
    balance:     Optional[int] = None
    memo:        str = ''
    row_hash:    str = ''


class ImportRequest(BaseModel):
    bank_name: str = ''
    rows:      List[ImportRow]


@router.post("/import", summary="CSV取込確定")
def import_rows(req: ImportRequest):
    """プレビュー確認後、新規行のみDBに取込む"""
    if not req.rows:
        raise HTTPException(status_code=400, detail="取込データがありません")

    batch_id = str(uuid.uuid4())
    inserted = 0
    skipped  = 0

    with get_connection() as conn, conn.cursor() as cur:
        for row in req.rows:
            # row_hash が空の場合は再計算
            rh = row.row_hash or _make_hash(
                row.tx_date, row.description, row.debit, row.credit, row.balance
            )
            cur.execute("""
                INSERT INTO bank_statement_rows
                    (tx_date, description, debit, credit, balance,
                     memo, bank_name, import_batch_id, row_hash)
                VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
                ON CONFLICT (row_hash) DO NOTHING
            """, (
                row.tx_date, row.description,
                row.debit, row.credit, row.balance,
                row.memo, req.bank_name, batch_id, rh,
            ))
            if cur.rowcount > 0:
                inserted += 1
            else:
                skipped += 1
        conn.commit()

    return {
        'batch_id': batch_id,
        'inserted': inserted,
        'skipped':  skipped,
    }


@router.get("", summary="明細一覧取得")
def list_statements(
    year:      Optional[int] = None,
    month:     Optional[int] = None,
    bank_name: Optional[str] = None,
    date_from: Optional[str] = None,
    date_to:   Optional[str] = None,
    limit:     int = 10000,
    offset:    int = 0,
):
    conditions: list = []
    params:     list = []

    if date_from:
        conditions.append("tx_date >= %s::date")
        params.append(date_from)
    if date_to:
        conditions.append("tx_date <= %s::date")
        params.append(date_to)
    # date_from/date_to が両方未指定の場合のみ year/month フォールバック
    if not date_from and not date_to:
        if year:
            conditions.append("EXTRACT(YEAR FROM tx_date)::int = %s")
            params.append(year)
        if month:
            conditions.append("EXTRACT(MONTH FROM tx_date)::int = %s")
            params.append(month)
    if bank_name:
        conditions.append("bank_name = %s")
        params.append(bank_name)

    where_clause = ("WHERE " + " AND ".join(conditions)) if conditions else ""

    with get_connection() as conn, conn.cursor() as cur:
        # 明細行
        cur.execute(f"""
            SELECT id,
                   TO_CHAR(tx_date, 'YYYY-MM-DD') AS tx_date,
                   description,
                   debit::bigint,
                   credit::bigint,
                   balance::bigint,
                   memo,
                   bank_name,
                   import_batch_id
            FROM bank_statement_rows
            {where_clause}
            ORDER BY tx_date, id
            LIMIT %s OFFSET %s
        """, params + [limit, offset])
        rows = cur.fetchall()

        # 月別集計
        cur.execute(f"""
            SELECT
                TO_CHAR(tx_date, 'YYYY-MM')  AS ym,
                SUM(debit)::bigint            AS total_debit,
                SUM(credit)::bigint           AS total_credit,
                COUNT(*)::int                 AS count
            FROM bank_statement_rows
            {where_clause}
            GROUP BY ym
            ORDER BY ym
        """, params)
        monthly = cur.fetchall()

        # 総件数
        cur.execute(
            f"SELECT COUNT(*)::int AS cnt FROM bank_statement_rows {where_clause}",
            params
        )
        total = cur.fetchone()['cnt']

        # 銀行名リスト
        cur.execute("""
            SELECT DISTINCT bank_name
            FROM bank_statement_rows
            WHERE bank_name != ''
            ORDER BY bank_name
        """)
        banks = [r['bank_name'] for r in cur.fetchall()]

    return {
        'total':   total,
        'rows':    rows,
        'monthly': monthly,
        'banks':   banks,
    }


@router.get("/batches", summary="取込バッチ一覧")
def list_batches():
    with get_connection() as conn, conn.cursor() as cur:
        cur.execute("""
            SELECT
                import_batch_id,
                bank_name,
                MIN(tx_date)::text     AS date_from,
                MAX(tx_date)::text     AS date_to,
                COUNT(*)::int          AS count,
                MIN(imported_at)::text AS imported_at
            FROM bank_statement_rows
            GROUP BY import_batch_id, bank_name
            ORDER BY MIN(imported_at) DESC
            LIMIT 100
        """)
        return cur.fetchall()


@router.delete("/batch/{batch_id}", summary="バッチ削除")
def delete_batch(batch_id: str):
    with get_connection() as conn, conn.cursor() as cur:
        cur.execute(
            "DELETE FROM bank_statement_rows WHERE import_batch_id = %s",
            (batch_id,)
        )
        deleted = cur.rowcount
        conn.commit()
    return {'deleted': deleted}
