"""
ねんきんポータル ZIPパーサー
対応文書種別:
  SHAKAI  - 社会保険料額情報
  NOUNYU  - 保険料納入告知額・領収済額通知書
  SHOYO   - 賞与保険料算出内訳書
"""
import zipfile
import io
import re
import xml.etree.ElementTree as ET
from datetime import date
from typing import Optional, Dict, Any

# ──────────────────────────────────────
# 元号オフセット
# ──────────────────────────────────────
GENGOU_OFFSETS = {
    '令和': 2018, 'R': 2018,
    '平成': 1988, 'H': 1988,
    '昭和': 1925, 'S': 1925,
    '大正': 1911, 'T': 1911,
    '明治': 1867, 'M': 1867,
}

# XSLファイル名 → 文書種別
XSL_TYPE_MAP = {
    'yoshiki_04_shakai_003.xsl': 'SHAKAI',
    'yoshiki_29_zumitsu_001.xsl': 'NOUNYU',
    'yoshiki_03_shoyo_002.xsl': 'SHOYO',
}

TYPE_NAMES = {
    'SHAKAI': '社会保険料額情報',
    'NOUNYU': '保険料納入告知額・領収済額通知書',
    'SHOYO': '賞与保険料算出内訳書',
    'HIHOKEN': '被保険者データ',
    'ZOGENS': '保険料増減内訳書',
}


# ──────────────────────────────────────
# ユーティリティ
# ──────────────────────────────────────

def gengou_to_western(gengou: str, year_str) -> Optional[int]:
    try:
        offset = GENGOU_OFFSETS.get(str(gengou), 0)
        return int(str(year_str)) + offset if offset else None
    except Exception:
        return None


def make_date(gengou: str, year_str, month_str, day_str='1') -> Optional[date]:
    try:
        western = gengou_to_western(gengou, year_str)
        if not western:
            return None
        return date(western, int(month_str), int(day_str or 1))
    except Exception:
        return None


def parse_reki_date(s: str) -> Optional[date]:
    """R080430 → date(2026,4,30)"""
    if not s or len(s) < 7:
        return None
    prefix = s[0].upper()
    try:
        yy = int(s[1:3])
        mm = int(s[3:5])
        dd = int(s[5:7])
        offset = GENGOU_OFFSETS.get(prefix, 0)
        if not offset:
            return None
        return date(yy + offset, mm, dd)
    except Exception:
        return None


def parse_amount(s) -> Optional[float]:
    """'94,054' / '\\247,066' → float"""
    if s is None:
        return None
    cleaned = str(s).replace(',', '').replace('\\', '').replace('￥', '').replace('¥', '').strip()
    if not cleaned:
        return None
    try:
        return float(cleaned)
    except Exception:
        return None


def get_text(elem, tag: str) -> str:
    if elem is None:
        return ''
    child = elem.find(tag)
    return child.text.strip() if child is not None and child.text else ''


# ──────────────────────────────────────
# 文書種別の自動判定
# ──────────────────────────────────────

def detect_doc_type(names: list) -> str:
    for name in names:
        basename = name.split('/')[-1]
        if basename in XSL_TYPE_MAP:
            return XSL_TYPE_MAP[basename]
    return 'UNKNOWN'


def get_document_no(zf, names: list) -> str:
    """封筒XML から文書番号を取得"""
    envelope_name = next(
        (n for n in names if re.match(r'.*/\d+\.xml$', n)),
        None
    )
    if not envelope_name:
        return ''
    with zf.open(envelope_name) as f:
        etree = ET.parse(f)
    elem = etree.getroot().find('.//DOCNO')
    return elem.text.strip() if elem is not None and elem.text else ''


# ──────────────────────────────────────
# メインエントリー
# ──────────────────────────────────────

def parse_zip(zip_bytes: bytes, zip_filename: str) -> Dict[str, Any]:
    zf = zipfile.ZipFile(io.BytesIO(zip_bytes))
    names = zf.namelist()
    doc_type = detect_doc_type(names)

    if doc_type == 'SHAKAI':
        return _parse_shakai(zf, names, zip_filename)
    elif doc_type == 'NOUNYU':
        return _parse_nounyu(zf, names, zip_filename)
    elif doc_type == 'SHOYO':
        return _parse_shoyo(zf, names, zip_filename)
    else:
        raise ValueError(f"未対応の文書種別です（XSLが不明）: {[n.split('/')[-1] for n in names]}")


# ──────────────────────────────────────
# 社会保険料額情報 (SHAKAI)
# ──────────────────────────────────────

def _parse_shakai(zf, names: list, zip_filename: str) -> Dict[str, Any]:
    xml_name = next(
        n for n in names
        if '社会保険料額情報' in n.split('/')[-1] and n.endswith('.xml')
    )
    with zf.open(xml_name) as f:
        root = ET.parse(f).getroot()

    lst = root.find('shakaiHokenRyougakuList')
    header = lst.find('header')

    jigyosho_name = get_text(header, 'jigyoshoName').replace('<br/>', ' ')
    jigyosho_num  = get_text(header, 'jigyoshoNum')
    jigyosho_kigo = get_text(header, 'jigyoshoSeiriKigo')
    nenkin_jimusho = get_text(header, 'nenkinJimusho1')
    nofu_gengou   = get_text(header, 'nouhuMokutekiYearGengou')
    nofu_year     = get_text(header, 'nouhuMokutekiYear')
    nofu_month    = get_text(header, 'nouhuMokutekiMonth')

    hakkou_gengou = get_text(lst, 'hakkouYmdGengou')
    hakkou_year   = get_text(lst, 'hakkouYmdYear')
    hakkou_month  = get_text(lst, 'hakkouYmdMonth')
    hakkou_day    = get_text(lst, 'hakkouYmdDate')

    nofu_kigen_gengou = get_text(lst, 'nouhuKigenGengou')
    nofu_kigen_year   = get_text(lst, 'nouhuKigenYear')
    nofu_kigen_month  = get_text(lst, 'nouhuKigenMonth')
    nofu_kigen_day    = get_text(lst, 'nouhuKigenDate')

    return {
        'document_no':        get_document_no(zf, names),
        'document_type':      'SHAKAI',
        'document_type_name': TYPE_NAMES['SHAKAI'],
        'zip_filename':       zip_filename,
        'office_name':        jigyosho_name,
        'office_number':      jigyosho_num,
        'office_kigo':        jigyosho_kigo,
        'nenkin_jimusho':     nenkin_jimusho,
        'purpose_year':       gengou_to_western(nofu_gengou, nofu_year),
        'purpose_month':      int(nofu_month) if nofu_month else None,
        'issue_date':         make_date(hakkou_gengou, hakkou_year, hakkou_month, hakkou_day),
        'detail': {
            'kenkou_hokenryo':        parse_amount(get_text(lst, 'kenkouHokenRyou')),
            'kousei_nenkin_hokenryo': parse_amount(get_text(lst, 'kouseiNenkinHokenRyou')),
            'kodomo_kyoshutsukin':    parse_amount(get_text(lst, 'kodomoKosodateKyoshutsuKin')),
            'total':                  parse_amount(get_text(lst, 'total')),
            'nofu_kigen_date':        make_date(nofu_kigen_gengou, nofu_kigen_year, nofu_kigen_month, nofu_kigen_day),
            'office_postcode':        get_text(lst, 'jigyoushoPostCode'),
            'office_address':         get_text(lst, 'jigyoushoShozaichi').replace('<br/>', ' '),
        }
    }


# ──────────────────────────────────────
# 保険料納入告知額・領収済額通知書 (NOUNYU)
# ──────────────────────────────────────

def _parse_nounyu(zf, names: list, zip_filename: str) -> Dict[str, Any]:
    xml_name = next(
        n for n in names
        if '保険料納入告知額' in n.split('/')[-1] and n.endswith('.xml')
    )
    with zf.open(xml_name) as f:
        root = ET.parse(f).getroot()

    lst = root.find('hokenRyouRyoshuzumigakutsuchiShoList')
    header = lst.find('header')

    jigyosho_num  = get_text(header, 'jigyoshoNum')
    jigyosho_kigo = get_text(header, 'jigyoshoSeiriKigo')
    nenkin_jimusho = get_text(header, 'nenkinJimusho1')
    nofu_gengou   = get_text(header, 'nouhuMokutekiYearGengou')
    nofu_year     = get_text(header, 'nouhuMokutekiYear')
    nofu_month    = get_text(header, 'nouhuMokutekiMonth')

    hakkou_gengou = get_text(lst, 'hakkouYmdGengou')
    hakkou_year   = get_text(lst, 'hakkouYmdYear')
    hakkou_month  = get_text(lst, 'hakkouYmdMonth')
    hakkou_day    = get_text(lst, 'hakkouYmdDate')

    nofu_kigen_gengou = get_text(lst, 'nouhuKigenGengou')
    nofu_kigen_year   = get_text(lst, 'nouhuKigenYear')
    nofu_kigen_month  = get_text(lst, 'nouhuKigenMonth')
    nofu_kigen_day    = get_text(lst, 'nouhuKigenDate')

    ryoshu_gengou = get_text(lst, 'ryoshuDtGengo')
    ryoshu_year   = get_text(lst, 'ryoshuDtYear')
    ryoshu_month  = get_text(lst, 'ryoshuDtMonth')
    ryoshu_day    = get_text(lst, 'ryoshuDtDate')

    ryoshu_nofu_gengou = get_text(lst, 'ryoshuNouhuMokutekiYmGengou')
    ryoshu_nofu_year   = get_text(lst, 'ryoshuNouhuMokutekiYmYear')
    ryoshu_nofu_month  = get_text(lst, 'ryoshuNouhuMokutekiYmMonth')

    name1 = get_text(lst, 'jigyoshoNameLine1')
    name2 = get_text(lst, 'jigyoshoNameLine2')
    office_name = (name1 + ' ' + name2).strip()

    return {
        'document_no':        get_document_no(zf, names),
        'document_type':      'NOUNYU',
        'document_type_name': TYPE_NAMES['NOUNYU'],
        'zip_filename':       zip_filename,
        'office_name':        office_name,
        'office_number':      jigyosho_num,
        'office_kigo':        jigyosho_kigo,
        'nenkin_jimusho':     nenkin_jimusho,
        'purpose_year':       gengou_to_western(nofu_gengou, nofu_year),
        'purpose_month':      int(nofu_month) if nofu_month else None,
        'issue_date':         make_date(hakkou_gengou, hakkou_year, hakkou_month, hakkou_day),
        'detail': {
            'kokuchi_kenkou':     parse_amount(get_text(lst, 'kenkouHokenRyou')),
            'kokuchi_kounen':     parse_amount(get_text(lst, 'kouseiNenkinHokenRyou')),
            'kokuchi_kodomo':     parse_amount(get_text(lst, 'kodomoKosodateKyoshutsuKin')),
            'kokuchi_total':      parse_amount(get_text(lst, 'total')),
            'kokuchi_nofu_kigen': make_date(nofu_kigen_gengou, nofu_kigen_year, nofu_kigen_month, nofu_kigen_day),
            'ryoshu_kenkou':      parse_amount(get_text(lst, 'ryoshuKenkouHokenRyou')),
            'ryoshu_kounen':      parse_amount(get_text(lst, 'ryoshuKouseiNenkinHokenRyou')),
            'ryoshu_kodomo':      parse_amount(get_text(lst, 'ryoshuKodomoKosodateKyoshutsuKin')),
            'ryoshu_total':       parse_amount(get_text(lst, 'ryoshuTotal')),
            'ryoshu_date':        make_date(ryoshu_gengou, ryoshu_year, ryoshu_month, ryoshu_day),
            'ryoshu_nofu_year':   gengou_to_western(ryoshu_nofu_gengou, ryoshu_nofu_year),
            'ryoshu_nofu_month':  int(ryoshu_nofu_month) if ryoshu_nofu_month else None,
            'office_postcode':    get_text(lst, 'jigyoushoPostCode'),
            'office_address':     (get_text(lst, 'jigyoushoShozaichiLine1') + ' ' + get_text(lst, 'jigyoushoShozaichiLine2')).strip(),
            'office_name_line1':  name1,
            'office_name_line2':  name2,
        }
    }


# ──────────────────────────────────────
# 賞与保険料算出内訳書 (SHOYO)
# ──────────────────────────────────────

def _parse_shoyo(zf, names: list, zip_filename: str) -> Dict[str, Any]:
    # メインXML（.xmlかつ「賞与保険料算出内訳書」を含むもの）
    xml_name = next(
        n for n in names
        if '賞与保険料算出内訳書' in n.split('/')[-1]
        and n.endswith('.xml')
    )
    with zf.open(xml_name) as f:
        root = ET.parse(f).getroot()

    shoyo_list = root.find('shouyoList')
    header     = shoyo_list.find('header')

    jigyosho_name  = get_text(header, 'jigyoshoName')
    jigyosho_num   = get_text(header, 'jigyoshoNum')
    jigyosho_kigo  = get_text(header, 'jigyoshoSeiriKigo')
    nenkin_jimusho = get_text(header, 'nenkinJimusho2').replace('\u3000', ' ').strip()
    nofu_gengou    = get_text(header, 'nouhuMokutekiYearGengou')
    nofu_year      = get_text(header, 'nouhuMokutekiYear')
    nofu_month     = get_text(header, 'nouhuMokutekiMonth')
    jin_in         = get_text(header, 'jinInNum')
    menjyo         = get_text(header, 'menjyoHokenRyouRitu')

    # 各被保険者の明細
    items = []
    for uchiwake in shoyo_list.findall('uchiwake'):
        hyojun     = uchiwake.find('hyoujyunShouyoGaku')
        kenpo_ryou = uchiwake.find('kenKouHokenRyou')
        kounen_ryou = uchiwake.find('kouseiNenkinHokenRyou')

        hassei_ymd = get_text(hyojun, 'hasseiYMD') if hyojun is not None else ''
        parsed_hassei = parse_reki_date(hassei_ymd)

        items.append({
            'seiri_num':           get_text(uchiwake, 'seiriNum').strip(),
            'shimei':              get_text(uchiwake, 'shimei'),
            'shori_ymd':           get_text(uchiwake, 'shoriYMD'),
            'hassei_ymd':          hassei_ymd,
            'hassei_date_iso':     parsed_hassei.isoformat() if parsed_hassei else None,
            'hyojun_shoyo_kenpo':  parse_amount(get_text(hyojun, 'getsuGakuKenpo') if hyojun is not None else ''),
            'hyojun_shoyo_kounen': parse_amount(get_text(hyojun, 'getsuGakuKounen') if hyojun is not None else ''),
            'kenpo_hongetsu':      parse_amount(get_text(kenpo_ryou, 'hongetsuGaku') if kenpo_ryou is not None else ''),
            'kenpo_zengetsu':      parse_amount(get_text(kenpo_ryou, 'zengetsuIzenMonth') if kenpo_ryou is not None else ''),
            'kounen_hongetsu':     parse_amount(get_text(kounen_ryou, 'hongetsuGaku') if kounen_ryou is not None else ''),
            'kounen_zengetsu':     parse_amount(get_text(kounen_ryou, 'zengetsuIzenMonth') if kounen_ryou is not None else ''),
            'page_num':            int(get_text(uchiwake, 'pageNum') or 0),
        })

    return {
        'document_no':        get_document_no(zf, names),
        'document_type':      'SHOYO',
        'document_type_name': TYPE_NAMES['SHOYO'],
        'zip_filename':       zip_filename,
        'office_name':        jigyosho_name,
        'office_number':      jigyosho_num,
        'office_kigo':        jigyosho_kigo,
        'nenkin_jimusho':     nenkin_jimusho,
        'purpose_year':       gengou_to_western(nofu_gengou, nofu_year),
        'purpose_month':      int(nofu_month) if nofu_month else None,
        'issue_date':         None,
        'detail': {
            'jin_in_num':           int(jin_in) if jin_in else 0,
            'menjyo_hokenryo_ritsu': menjyo,
            'page_num':             int(get_text(shoyo_list, 'pageNum') or 1),
            'items':                items,
        }
    }
