"""Reproduce the APLNG money trail from pinned original reports.

Run beside the national-outcomes manifest with the bundled Python runtime.
Default is read-only verification; --write refreshes the derived JSON only.
The reports are issuer disclosures, not an independent project audit.
"""
from pathlib import Path
import argparse
import hashlib
import json
import re
from pypdf import PdfReader

ROOT = Path(__file__).resolve().parent
parser = argparse.ArgumentParser()
parser.add_argument('--write', action='store_true')
args = parser.parse_args()
manifest = {x['id']: x for x in json.loads((ROOT/'manifest.json').read_text())}
sources = {}
readers = {}
for key in ['origin-annual-2025-sgx', 'origin-annual-2026-sgx', 'aplng-tax-2025']:
    entry = manifest[key]
    path = ROOT/entry['path']
    assert hashlib.sha256(path.read_bytes()).hexdigest() == entry['sha256'], key
    readers[key] = PdfReader(path)
    sources[key] = {k:entry[k] for k in ['url','path','sha256','retrievedAtUtc']}

controls = []
def page(source, number):
    return '\n'.join(' '.join(x.split()) for x in readers[source].pages[number-1].extract_text().splitlines())

def numbers(source, number, label, count=2, section=None):
    text = page(source, number)
    if section:
        text = text.split(section[0],1)[1].split(section[1],1)[0]
    lines = [x for x in text.splitlines() if x.startswith(label+' ')]
    assert len(lines)==1, (source,number,label,lines)
    tail=lines[0][len(label):].strip()
    tokens=tail.split()
    assert len(tokens)==count and all(re.fullmatch(r'\(?[\d,]+\)?|-',t) for t in tokens), (label,tokens)
    values=[0 if t=='-' else int(t.strip('()').replace(',',''))*(-1 if t.startswith('(') else 1) for t in tokens]
    controls.append({'source':source,'pdfPage':number,'label':label,'section':list(section) if section else None,'valuesInSourceOrder':values})
    return values

cash_labels={
    'customers':'Receipts from customers', 'suppliersEmployees':'Payments to suppliers and employees',
    'operationsBeforeTax':'Net cash from operations', 'incomeTax':'Income tax paid',
    'operatingCash':'Net cash from operating activities', 'shareholderLoans':'Loans advanced to other shareholders',
    'plantEquipment':'Acquisition of PP&E', 'courtProceeds':'Proceeds from court judgment(1)',
    'explorationDevelopment':'Acquisition of exploration and development assets',
    'assetSales':'Proceeds from sale of assets', 'interestReceived':'Interest received',
    'investingCash':'Net cash used in investing activities', 'leasePrincipal':'Repayment of lease principal',
    'leaseInterest':'Payment of interest on lease liabilities', 'newBorrowing':'Proceeds from borrowings',
    'repaidBorrowing':'Repayment of borrowings',
    'borrowingInterestFees':'Payments of transaction and interest costs relating to borrowings',
    'dividends':'Payments of ordinary dividends', 'financingCash':'Net cash used in financing activities',
    'change':'Net (decrease)/increase in cash and cash equivalents',
    'openingCash':'Cash and cash equivalents at the beginning of the year',
    'exchangeMovement':'Effect of exchange rate changes on cash',
    'closingCash':'Cash and cash equivalents at the end of the year',
}
cash={y:{} for y in ['FY2026','FY2025']}
for key,label in cash_labels.items():
    values=numbers('origin-annual-2026-sgx',110,label)
    for y,v in zip(cash,values): cash[y][key]=v
    if key in ['courtProceeds','newBorrowing']:
        assert values[1]==0
        continue
    previous_label='Net increase in cash and cash equivalents' if key=='change' else label
    assert numbers('origin-annual-2025-sgx',107,previous_label)[0]==values[1], ('Comparative changed',key)

income={y:{} for y in cash}
for key,label,count in [('revenue','Operating revenue',2),('operatingExpenses','Operating expenses',2),
    ('ebitda','EBITDA',4),('depreciation','Depreciation and amortisation expense',4),
    ('interestIncome','Interest income',4),('interestExpense','Interest expense',4),
    ('taxExpense','Income tax expense',4),('profit','Statutory result for the year',4)]:
    values=numbers('origin-annual-2026-sgx',108,label,count)
    for y,v in zip(income,[values[0],values[1 if count==2 else 2]]): income[y][key]=v

sales={y:{} for y in cash}
for key,label,section in [
    ('productionPJ','Total production',None),('purchasesPJ','Purchases',None),
    ('upstreamInventoryOtherPJ','Changes in upstream gas inventory/other',None),
    ('downstreamInventoryOtherPJ','Liquefaction/downstream inventory/other',None),('totalSalesPJ','Total sales',None),
    ('domesticRevenue','Domestic gas',('Commodity revenue ($m)','Sales mix (PJ)')),
    ('lngRevenue','LNG',('Commodity revenue ($m)','Sales mix (PJ)')),
    ('domesticPJ','Domestic gas',('Sales mix (PJ)','Realised price')),
    ('contractLNGPJ','LNG contract',None),('spotLNGPJ','LNG spot',None)]:
    values=numbers('origin-annual-2026-sgx',46,label,4,section)
    for y,v in zip(sales,[values[0],values[2]]): sales[y][key]=v

debt={}
for key,label in [('bank','Bank loan (variable)'),('usExim','US Exim'),('usPrivatePlacement','USPP'),('grossTotal','Total')]:
    # EXIM row in the original omits its final zero, so preserve its printed row width.
    debt[key]=numbers('origin-annual-2026-sgx',35,label,6 if key=='usExim' else 7)[0]
assert sum(debt[k] for k in ['bank','usExim','usPrivatePlacement'])==debt['grossTotal']
debt['unamortisedFees']=15
debt['netCarryingValue']=3042
assert 'US$15' in page('origin-annual-2026-sgx',35) and 'US$3,042' in page('origin-annual-2026-sgx',35)
assert debt['grossTotal']-debt['unamortisedFees']==debt['netCarryingValue']

government={}
for key,label in [('incomeTax','Income tax'),('royalties','Government royalties'),('total','Total')]:
    government[key]=numbers('aplng-tax-2025',6,label,3)
assert government['incomeTax'][1]==-cash['FY2025']['incomeTax']
for i in range(3): assert government['incomeTax'][i]+government['royalties'][i]==government['total'][i]
tax_accounting=numbers('aplng-tax-2025',8,'Total Income Tax Expense/(Benefit)',2)
assert tax_accounting[0]==1430
tax_bridge={}
for key,label in [('income','Total income'),('expenses','Less accounting expenses'),('profit','Accounting profit before tax'),
    ('depreciation','Tax depreciation and other capital allowance deductions'),('fx','Project Finance - FX loss deduction'),
    ('other','Other adjustments'),('taxable','Taxable income'),('grossTax','Gross tax payable at 30%'),
    ('offset','Franking deficit tax offset'),('payable','Income tax payable')]:
    tax_bridge[key]=numbers('aplng-tax-2025',5,label,1)[0]
assert tax_bridge['income']+tax_bridge['expenses']==tax_bridge['profit']
assert sum(tax_bridge[k] for k in ['profit','depreciation','fx','other'])==tax_bridge['taxable']
assert round(tax_bridge['taxable']*0.30)==tax_bridge['grossTax']
assert tax_bridge['grossTax']+tax_bridge['offset']==tax_bridge['payable']

for y,c in cash.items():
    assert c['customers']+c['suppliersEmployees']==c['operationsBeforeTax']
    assert c['operationsBeforeTax']+c['incomeTax']==c['operatingCash']
    assert sum(c[k] for k in ['shareholderLoans','plantEquipment','courtProceeds','explorationDevelopment','assetSales','interestReceived'])==c['investingCash']
    assert sum(c[k] for k in ['leasePrincipal','leaseInterest','newBorrowing','repaidBorrowing','borrowingInterestFees','dividends'])==c['financingCash']
    assert c['operatingCash']+c['investingCash']+c['financingCash']==c['change']
    assert c['openingCash']+c['change']+c['exchangeMovement']==c['closingCash']
    a=income[y]
    assert a['revenue']+a['operatingExpenses']==a['ebitda']
    assert sum(a[k] for k in ['ebitda','depreciation','interestIncome','interestExpense','taxExpense'])==a['profit']
    s=sales[y]
    assert sum(s[k] for k in ['productionPJ','purchasesPJ','upstreamInventoryOtherPJ','downstreamInventoryOtherPJ'])==s['totalSalesPJ']
    assert s['domesticPJ']+s['contractLNGPJ']+s['spotLNGPJ']==s['totalSalesPJ']

result={
    'checkedOn':'2026-10-04','entity':'Australia Pacific LNG','scope':'100% APLNG, not Origin Group or its 27.5% share',
    'cashUnit':'AUD million; cash received positive, cash paid negative','sources':sources,
    'cashFlow':cash,'incomeStatement':income,'sales':sales,
    'debt':{'date':'2026-06-30','unit':'USD million','balances':debt,'futureColumnsExcluded':True},
    'governmentPaymentsTaxReport':{'columnOrder':['FY2026 preliminary','FY2025','FY2024'],'unit':'AUD million','values':government},
    'incomeTaxBridgeFY2025':{'unit':'AUD million, rounded source table','values':tax_bridge},
    'discrepancies':[
        {'item':'FY2026 income tax cash','taxReportPreliminary':1410,'originFinalReport':1414,'difference':4,'unit':'AUD million','resolution':'Use 1414 for the final cash statement. Do not silently replace the earlier source or combine it with preliminary royalties to claim a final public-payment total.'},
        {'item':'FY2025 income tax expense','aplngTaxReport':1430,'originReport':1424,'difference':6,'unit':'AUD million','resolution':'Separate publisher translations/measurement presentations; cause not independently reconciled. Both are distinct from tax payable and tax cash.'}
    ],
    'controls':controls,
    'checks':['Three source hashes','Original FY2025 cash rows equal FY2026 comparatives','Operating, investing and financing subtotals','Opening to closing cash including exchange movement','Income statement bridge','Production and sales volume bridges','Debt tranches and fee reconciliation','Tax report payment subtotals and FY2025 cash match'],
    'limitations':['Issuer disclosures are not an independent project audit','No employee-only payroll or ultimate dividend-recipient geography','Government royalties are separately disclosed context; do not add them again to complete cash outflows','Net public return, Australian concessions and closure assurance remain unreconciled']
}
out=ROOT/'aplng-money-trail.json'
if args.write:
    out.write_text(json.dumps(result,indent=2,ensure_ascii=False)+'\n')
else:
    assert json.loads(out.read_text())==result, 'Derived record differs from originals'
print(json.dumps({'status':'passed','sourceHashes':len(sources),'sourceRows':len(controls),'years':list(cash),'cashReconciliation':{y:c['closingCash'] for y,c in cash.items()},'fy2026TaxDiscrepancyAUDm':4}))
