"""Parse SU_CaNotices items (Anunțuri de atribuire) → seap.announcements row dict."""
from __future__ import annotations

from ..xml_utils import (
    find_child_local, find_local, find_path,
    text_under, text_direct, int_under, decimal_under, bool_under,
    datetime_under, sysitem_name, sysitem_id, to_jsonable,
)

SOURCE = 'wsp_canotice'


def parse(el) -> dict | None:
    """Extract a CaNoticeBase element (V1 or V2) into our normalized dict."""
    notice_no = text_under(el, 'NoticeNo')
    notice_id = int_under(el, 'CaNoticeId')
    if not notice_no:
        return None

    general = find_child_local(el, 'General')
    section1 = find_child_local(el, 'Section1')
    section2 = find_child_local(el, 'Section2')
    section4 = find_child_local(el, 'Section4')
    section5 = find_child_local(el, 'Section5')

    # Authority block: Section1_1 → CaAddresses → CaEntityInformation → EntityInformation
    auth_entity = find_path(section1, 'Section1_1', 'CaAddresses')
    auth_info = None
    if auth_entity is not None:
        auth_info = find_local(auth_entity, 'EntityInformation')

    authority_name = text_direct(auth_info, 'Name') if auth_info is not None else None
    authority_cui = text_direct(auth_info, 'Cif') if auth_info is not None else None
    authority_address = text_direct(auth_info, 'Address') if auth_info is not None else None
    authority_email = text_direct(auth_info, 'Email') if auth_info is not None else None
    authority_phone = text_direct(auth_info, 'Phone') if auth_info is not None else None
    authority_url = text_direct(auth_info, 'Url') if auth_info is not None else None

    # NUTS code = county-ish (RO213 etc.)
    nuts_el = find_local(auth_info, 'NutsCode') if auth_info is not None else None
    county_code = sysitem_name(auth_info, 'NutsCode') if auth_info is not None else None

    # Authority type + main activity
    s1_4 = find_child_local(section1, 'Section1_4') if section1 is not None else None
    authority_type = sysitem_name(s1_4, 'ContractingAuthorityType')
    s1_5 = find_child_local(section1, 'Section1_5') if section1 is not None else None
    main_activity = sysitem_name(s1_5, 'MainActivity')

    # Section 2: contract details
    s2_1 = find_child_local(section2, 'Section2_1') if section2 is not None else None
    contract_title = text_under(general, 'ContractTitle') or text_under(s2_1, 'ContractName')
    short_desc = text_under(s2_1, 'ShortContractDescription')
    main_cpv_id = sysitem_id(s2_1, 'MainCPV')
    main_cpv_code = sysitem_name(s2_1, 'MainCPV')
    contract_type = sysitem_name(s2_1, 'SysAcquisitionContractType')
    currency = sysitem_name(s2_1, 'Currency')
    total_value = decimal_under(s2_1, 'TotalValue')
    highest_offer = decimal_under(s2_1, 'HighestOffer')
    lowest_offer = decimal_under(s2_1, 'LowestOffer')
    has_lots = bool_under(s2_1, 'ContractHasLots')
    reference_number = text_under(s2_1, 'ReferenceNumber')

    # Lots — Section 5 (ContractLotList) for awarded notices, Section 2_2 otherwise
    lots = _extract_lots(section5) or _extract_lots(section2)
    lots_count = len(lots) if lots else None

    # Award criteria — extracted from lot info or top-level
    award_criteria = _extract_award_criteria(el)

    # Section 4: procedure details
    s4_1 = find_child_local(section4, 'Section4_1') if section4 is not None else None
    procedure_type = sysitem_name(s4_1, 'SysProcedureType')
    framework_agreement = bool_under(s4_1, 'FrameworkAgreement')

    # Dates
    publication_date = datetime_under(general, 'PublishDate')
    legislation = sysitem_name(general, 'SysLegislationType')
    notice_state = sysitem_name(general, 'SysNoticeState')
    notice_state_id = sysitem_id(general, 'SysNoticeState')
    is_utility = bool_under(general, 'IsUtility')
    notice_no_joue = text_under(general, 'NoticeNoJoue')
    entity_id = int_under(general, 'EntityId')

    # Winners (suppliers) — extracted from lot info
    winners = _extract_winners(section5)
    # Pick first winner as primary; full list in lots JSONB
    primary_winner = winners[0] if winners else {}

    # Documents — DfNoticeFiles, CompanyFiles
    documents = _extract_documents(general)

    # Final award value
    awarded_value = total_value
    if not awarded_value and lots:
        # sum lot values
        try:
            awarded_value = sum(
                (lot.get('total_value') or 0) for lot in lots if lot.get('total_value')
            ) or None
        except Exception:
            pass

    return {
        # Standard columns
        'type': 'ca_notice',
        'ref_number': f'WSP-{notice_no}',
        'authority_name': authority_name,
        'authority_cui': authority_cui,
        'authority_address': authority_address,
        'authority_email': authority_email,
        'authority_phone': authority_phone,
        'authority_url': authority_url,
        'authority_type': authority_type,
        'authority_main_activity': main_activity,
        'authority_entity_id': entity_id,
        'title': contract_title[:1000] if contract_title else None,
        'cpv_code': main_cpv_code,
        'contract_type': contract_type,
        'publication_date': publication_date,
        'estimated_value': None,
        'awarded_value': awarded_value,
        'currency': currency,
        'supplier_name': primary_winner.get('name'),
        'supplier_cui': primary_winner.get('cif'),
        'supplier_address': primary_winner.get('address'),
        'supplier_is_sme': primary_winner.get('is_sme'),
        'procedure_type': procedure_type,
        'procedure_state': notice_state,
        'legislation': legislation,
        'lot_number': None,
        'has_lots': 'da' if has_lots else 'nu' if has_lots is False else None,
        'contract_has_lots': has_lots,
        'lots_count': lots_count,
        'joue': notice_no_joue,
        'county_code': county_code,
        'notice_state': notice_state,
        'notice_state_id': notice_state_id,
        'framework_agreement': framework_agreement,
        'notice_id_internal': notice_id,
        'seap_url': f'https://e-licitatie.ro/pub/notices/contract-award-notice/{notice_id}' if notice_id else None,

        # Rich JSONB
        'documents': documents or None,
        'award_criteria': award_criteria or None,
        'lots': lots or None,
        'details': {
            'short_description': short_desc,
            'reference_number': reference_number,
            'main_cpv_id': main_cpv_id,
            'highest_offer': str(highest_offer) if highest_offer else None,
            'lowest_offer': str(lowest_offer) if lowest_offer else None,
            'is_utility': is_utility,
            'all_winners': winners or None,
        },
        'source': SOURCE,
    }


def _extract_lots(section) -> list[dict]:
    """Extract lot info — handles ContractLotList (Section5) or Lots (Section2_2)."""
    if section is None:
        return []
    lot_list = find_local(section, 'ContractLotList')
    if lot_list is None:
        lot_list = find_local(section, 'Lots')
    if lot_list is None:
        return []
    out = []
    for lot in lot_list:
        if etree.QName(lot.tag).localname not in ('ContractLotInfo', 'LotInfo'):
            continue
        lot_data = {
            'lot_id': int_under(lot, 'LotID') or int_under(lot, 'ContractNo'),
            'lot_no': int_under(lot, 'LotNo') or int_under(lot, 'ContractNo'),
            'title': text_under(lot, 'Title') or text_under(lot, 'ContractObjectName'),
            'description': text_under(lot, 'DescriptionOfProcurement') or text_under(lot, 'ContractObjectDescription'),
            'cpv_code': sysitem_name(lot, 'MainCPVCode'),
            'estimated_value': _decimal_or_none(text_under(lot, 'EstimatedValue')),
            'total_value': _decimal_or_none(text_under(lot, 'TotalValue')),
            'duration_months': int_under(lot, 'DurationInMonths'),
            'duration_days': int_under(lot, 'DurationInDays'),
            'currency': sysitem_name(lot, 'Currency'),
            'place_of_performance': text_under(lot, 'MainSiteOrPlaceOfPerformance'),
            'is_community_financed': bool_under(lot, 'IsCommunityFinanced'),
            'has_options': bool_under(lot, 'HasOptions'),
            'awarded_to_group': bool_under(lot, 'AwardedToGroupOfEcoOp'),
        }
        # Convert decimals to str for JSON safety
        for k in ('estimated_value', 'total_value'):
            if lot_data[k] is not None:
                lot_data[k] = str(lot_data[k])
        # only keep non-empty
        out.append({k: v for k, v in lot_data.items() if v is not None})
    return out


def _extract_winners(section5) -> list[dict]:
    """Extract winner info from ContractLotList → ContractorAddressList."""
    if section5 is None:
        return []
    out = []
    seen_cifs = set()
    lot_list = find_local(section5, 'ContractLotList')
    if lot_list is None:
        return []
    for lot in lot_list:
        addr_list = find_local(lot, 'ContractorAddressList')
        if addr_list is None:
            continue
        for sect in addr_list:
            entity = find_local(sect, 'EntityInformation')
            if entity is None:
                continue
            cif = text_direct(entity, 'Cif')
            if cif in seen_cifs:
                continue
            if cif:
                seen_cifs.add(cif)
            out.append({
                'name': text_direct(entity, 'Name'),
                'cif': cif,
                'address': text_direct(entity, 'Address'),
                'email': text_direct(entity, 'Email'),
                'phone': text_direct(entity, 'Phone'),
                'url': text_direct(entity, 'Url'),
                'nuts_code': sysitem_name(entity, 'NutsCode'),
                'is_sme': bool_under(sect, 'IsSme'),
            })
    return out


def _extract_award_criteria(el) -> list[dict]:
    """Extract award criteria from lot info (MyPriceAwardCriterias / MyQualityAwardCriterias)."""
    out = []
    for crit_list_name in ('MyPriceAwardCriterias', 'MyQualityAwardCriterias'):
        for crit_list in el.iter():
            if etree.QName(crit_list.tag).localname != crit_list_name:
                continue
            for crit in crit_list:
                name = text_under(crit, 'Name') or text_under(crit, 'Description')
                weight = decimal_under(crit, 'Weight') or decimal_under(crit, 'Pondere')
                if name or weight:
                    item = {'type': 'price' if 'Price' in crit_list_name else 'quality',
                            'name': name}
                    if weight is not None:
                        item['weight'] = str(weight)
                    out.append(item)
    return out


def _extract_documents(general) -> list[dict]:
    """Extract document file references from DfNoticeFiles + CompanyFiles."""
    if general is None:
        return []
    out = []
    for fld in ('DfNoticeFiles', 'CompanyFiles', 'NoticeFiles'):
        container = find_local(general, fld)
        if container is None:
            continue
        for kvp in container:
            key = find_local(kvp, 'key')
            if key is None:
                key = find_local(kvp, 'Key')
            val = find_local(kvp, 'value')
            if val is None:
                val = find_local(kvp, 'Value')
            if key is not None and val is not None:
                out.append({
                    'type': fld,
                    'name': key.text,
                    'id': val.text,
                })
    return out


def _decimal_or_none(s):
    if not s:
        return None
    try:
        from decimal import Decimal
        return Decimal(s)
    except Exception:
        return None


# late import to avoid circular
from lxml import etree  # noqa: E402
