from pathlib import Path
from copy import copy
import json, subprocess, os, shutil, hashlib
from openpyxl import load_workbook, Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.comments import Comment
from openpyxl.worksheet.datavalidation import DataValidation
from pypdf import PdfReader
import pypdfium2 as pdfium

ROOT=Path(__file__).resolve().parents[1]
EV=ROOT/'evidence/server-research-20261007'
DATA=json.loads((EV/'server-data.json').read_text())
Q=ROOT/'output/智慧食堂软件报价单.xlsx'
V=ROOT/'output/智慧食堂软件版本功能对比.xlsx'
BACKUP=EV/'智慧食堂软件报价单_补充备注前.xlsx'
MONEY='#,##0" 元";[Red]-#,##0" 元";0" 元"'
NAVY,TEAL,PALE,BLUE='183B56','007F79','EFF6FA','1769AA'
FORMER=load_workbook(BACKUP)
W=load_workbook(Q)
before_hash=hashlib.sha256(V.read_bytes()).hexdigest()


def setup(s,widths):
    for c,w in enumerate(widths,1):
        from openpyxl.utils import get_column_letter
        s.column_dimensions[get_column_letter(c)].width=w
    s.sheet_view.showGridLines=False; s.sheet_view.zoomScale=85
    s.page_setup.paperSize=s.PAPERSIZE_A3; s.page_setup.orientation='landscape'
    s.page_setup.fitToWidth=1; s.page_setup.fitToHeight=1
    s.sheet_properties.pageSetUpPr.fitToPage=True
    s.page_margins.left=s.page_margins.right=.25
    s.page_margins.top=s.page_margins.bottom=.25
    s.oddFooter.center.text='2026-10-07 · 参考选型，非容量承诺'


def row(s,n,values,head=False,height=35):
    s.row_dimensions[n].height=height
    for c,v in enumerate(values,1):
        x=s.cell(n,c,v)
        x.font=Font(name='Microsoft YaHei',size=11,bold=head,color='FFFFFF' if head else NAVY)
        x.fill=PatternFill('solid',fgColor=TEAL if head else (PALE if n%2==0 else 'FFFFFF'))
        x.alignment=Alignment(vertical='center',wrap_text=True)
        x.border=Border(bottom=Side(style='hair',color='CBD8E2'))


def band(s,n,text,last,title=False,height=30):
    s.merge_cells(start_row=n,start_column=1,end_row=n,end_column=last)
    x=s.cell(n,1,text);x.font=Font(name='Microsoft YaHei',size=18 if title else 11,bold=title,color='FFFFFF' if title else NAVY)
    x.fill=PatternFill('solid',fgColor=NAVY if title else PALE)
    x.alignment=Alignment(vertical='center',wrap_text=True);s.row_dimensions[n].height=height


def urlcell(s,cell,label,url):
    x=s[cell];x.value=label;x.hyperlink=url;x.font=Font(name='Microsoft YaHei',size=11,color=BLUE,underline='single')


name='部署资源备注'
if name in W: del W[name]
S=W.create_sheet(name);setup(S,[22,45,48,34,30])
band(S,1,'部署规模与服务器费用备注',5,True,38)
band(S,2,'软件授权价不变；人数只触发资源评估。部署服务费、云年费、硬件采购分别计算。',5,height=32)
band(S,4,'01  人数档与配置 · 选型参考，不是已压测上限',5)
row(S,5,['人数参考档','云端资源起配','本地资源起配','数量/扩容关注点','依据'],True)
sizes=[
    ['≤1,000人','按8C16G应用＋4C8G数据库保守评估','合并单机可先评估8核/32G/双企业SSD','合并1台或应用/数据库拆2台；先测峰值','工程建议；非已验容量'],
    ['1,001～5,000人','应用8C16G/500GB/10Mbps×1；数据库4C8G/250GB','应用≥8核32G×1＋数据库≥4核16G×1；SSD/备份另配','云端1台应用＋1数据库；本地2节点参考','旧3千/5千档；本地为建议'],
    ['5,001～10,000人','应用8C16G/500GB×2＋数据库4C8G/250GB','应用≥8核32G×2＋数据库按负载评估','应用双机需负载均衡/共享状态；数据库HA另评估','旧1万人档；不保证DB已HA'],
    ['>10,000人或高峰集中','按压测增加应用、数据库与缓存资源','按压测确定节点数、CPU/RAM/SSD','增加人数不等于固定加1台；独立评估','工程建议；专项核价'],
]
for n,a in enumerate(sizes,6):row(S,n,a,height=64)
band(S,11,'峰值并发、设备数、订单量、备份/高可用共同决定资源；vCPU与物理核不能直接等同。',5,height=32)
band(S,13,'02  云服务器年度费用 · 参考报价档，不加入软件报价合计',5)
row(S,14,['人数区间','参考年费（元/年）','费用项目','状态','说明'],True)
for n,a in enumerate([
    ['≤5,000人',5000,'云服务器年度费用','用户参考示例；资源范围待核','我方参考价≠阿里云采购价'],
    ['5,001～10,000人',10000,'云服务器年度费用','沿例补充区间；待核价','资源不足时按配置重新报价'],
    ['10,001～15,000人',15000,'云服务器年度费用','延续建议，待确认','高可用及专属资源另评估'],
    ['>15,000人','每增加5,000人＋5,000元/年','云服务器年度费用','延续建议，待确认','不自动套入信创部署费'],
],15):row(S,n,a,height=38)
for n in [15,16,17]:S.cell(n,2).number_format=MONEY
row(S,20,['试算人数',20000,'每档人数',5000,'只作示例，不并入软件'],height=30)
row(S,21,['每档年费示例',5000,'年费推演','=IFERROR(IF(AND(ISNUMBER(B20),B20>=0,D20>0,ISNUMBER(B21),B21>=0),ROUNDUP(B20/D20,0)*B21,"待填写"),"待填写")','元/年；金额/资源范围待核'],height=30)
for c in ['B20','D20','B21']:
    S[c].font=Font(name='Microsoft YaHei',size=11,color=BLUE);S[c].fill=PatternFill('solid',fgColor='FFF3CF')
S['B21'].number_format=S['D21'].number_format=MONEY
d=DataValidation(type='whole',operator='greaterThanOrEqual',formula1=0,allow_blank=False);d.showErrorMessage=True;S.add_data_validation(d);d.add(S['B20'])
S['D21'].comment=Comment('用户已明确为云服务器年度费用；仅以每5000人5000元/年推演：20000人=20000元/年。不是阿里云市场价；递增与所含资源待核价。','价格示例')
band(S,23,'当前部署费仍为：云端0元、本地5,000元、信创20,000元（已含本地）；示例未覆盖原价。',5,height=32)
band(S,25,'03  阿里云核价 · 最低活动价不等于生产配置价',5)
row(S,26,['项目','公开金额','适用说明','费用边界','链接'],True)
row(S,27,['官网年度活动最低价','38元/年起','新客限时抢购，资格与库存需核对','不作为食堂生产预算',None],height=38)
urlcell(S,'E27','阿里云官方活动',DATA['cloud'][0]['url'])
row(S,28,['食堂生产配置','待按地域/配置核价','8C16G应用＋数据库、存储、网络','数据库/带宽/备份另计',None],height=38)
urlcell(S,'E28','官方核价入口',DATA['cloud'][2]['url'])
band(S,30,'本地机器和信创机型、配置、页面参考价及购买链接，见《服务器选型与价格参考.xlsx》。',5,height=32)
band(S,31,'查询：2026-10-07。公开参考价非登录到手价；国产OS/DB许可、UPS、备份和施工另核。',5,height=32)
S.freeze_panes='B6';S.print_area='A1:E31'

P=W['软件价格表']
allowed=[]
for label,remark in [('云端部署','服务器按规模另报；见资源备注'),('普通本地部署','服务器自备/另购；规模见资源备注'),('信创适配部署','含本地；环境/资源见备注')]:
    hits=[r for r in range(1,P.max_row+1) if P.cell(r,2).value==label]
    assert len(hits)==1
    cell=P.cell(hits[0],7);cell.value=remark
    cell.hyperlink="#'部署资源备注'!A1";allowed.append(('软件价格表',cell.coordinate))
    cell.comment=Comment('软件价与当前部署价保持；资源人数档及尚未批准的阶梯示例见部署资源备注。','本轮备注补充')
q=W['项目报价']
band(q,35,'查看部署人数档、服务器配置与费用说明 → 部署资源备注',7,height=30)
urlcell(q,'A35','查看部署人数档、服务器配置与费用说明 → 部署资源备注',"#'部署资源备注'!A1")
q.print_area='A1:G35'
W.save(Q)

R=Workbook();T=R.active;T.title='选型与价格';setup(T,[16,31,49,18,15,38,22])
band(T,1,'智慧食堂服务器选型与价格参考',7,True,38)
band(T,2,'核验日期：2026-10-07；页面参考价，不是登录订单价。只选服务器，不重复收软件授权。',7,height=32)
band(T,4,'01  阿里云年度费用',7)
row(T,5,['类别','产品/入口','配置/价格含义','公开金额','周期','购买条件/限制','链接'],True)
for n,x in enumerate(DATA['cloud'],6):
    row(T,n,['云资源',x['name'],'最低活动起价；具体套餐核对' if n==6 else ('官网活动推荐入口' if n==7 else '8C16G应用＋数据库/存储/网络'),x['price'] if x['price'] is not None else '待核价',x['period'],x['scope'],None],height=48)
    urlcell(T,f'G{n}','阿里云官网',x['url'])
band(T,10,'02  京东服务器候选 · 以下是对应主SKU的公开页面价',7)
row(T,11,['类别','型号','所列SKU配置','参考价（元/台）','采购性质','选型备注','商品链接'],True)
for n,x in enumerate(DATA['local'],12):
    row(T,n,[x['type'],x['model'],x['config'],x['price'],'硬件一次性',x['use'],None],height=49)
    T.cell(n,4).number_format=MONEY
    T.cell(n,4).comment=Comment('来源：'+x['url']+'\n查询日期：2026-10-07\n'+x['status']+'\n页面价格须按同SKU、税费、成色、保修和库存复核。','京东公开页面')
    urlcell(T,f'G{n}','京东商品页',x['url'])
band(T,19,'03  采购建议与费用边界',7)
for n,txt in enumerate([
    '普通入门：ST45 V3 / T160可作小规模或独立节点候选；四核SKU不能当成旧5000人档8核应用整套方案。',
    '普通8核：ST58 V3较接近应用节点；数据库仍需另配。阵列需明确是否含系统SSD，RAID不等于备份。',
    '信创预算型：KT301h G3先询32/64G内存＋双企业SSD升级价；8583元所列SKU只有16G，升级价另核。',
    '信创机架式：SR658H-D1可作海光候选；TaiShan为指定鲲鹏/ARM候选，先验证PHP/Java、设备SDK和数据库组合。',
    '采购前核对：全新/二手、ECC、企业SSD、阵列、双电、保修；国产OS和数据库正式许可/整机资质另核。',
    '云生产费用=应用＋数据库＋磁盘＋带宽/流量＋备份/高可用。38元活动机与原3000元预算不能直接等价。',
],20):band(T,n,txt,7,height=34)
T.freeze_panes='C12';T.auto_filter.ref='A11:G17';T.print_area='A1:G25'
F=ROOT/'output/服务器选型与价格参考.xlsx';R.save(F)

calc=EV/'recalculated';pdf=EV/'rendered'
calc.mkdir(exist_ok=True);pdf.mkdir(exist_ok=True)
env=os.environ.copy();env['FONTCONFIG_FILE']=str((ROOT/'evidence/fonts.conf').resolve());env['SAL_USE_VCLPLUGIN']='svp'
for fmt,dest in [('xlsx',calc),('pdf',pdf)]:
    a=subprocess.run(['soffice','--headless','--convert-to',fmt,'--outdir',str(dest.resolve()),str(Q.resolve()),str(F.resolve())],env=env,capture_output=True,text=True,timeout=90)
    assert a.returncode==0,(a.stdout,a.stderr)
    if fmt=='xlsx':
        for p in [Q,F]:assert (dest/p.name).exists();shutil.copy2(dest/p.name,p)

after=load_workbook(Q);changes=[]
for sheet in FORMER:
    for row_ in sheet:
        for c in row_:
            nv=after[sheet.title][c.coordinate].value
            if c.value!=nv: changes.append([sheet.title,c.coordinate,c.value,nv])
assert {(x[0],x[1]) for x in changes}==set(allowed),changes
assert hashlib.sha256(V.read_bytes()).hexdigest()==before_hash
values=load_workbook(Q,data_only=True)
assert values['项目报价']['G30'].value==5000
assert values['部署资源备注']['D21'].value==20000
group_rows=[r for r in range(1,values['软件价格表'].max_row+1) if values['软件价格表'].cell(r,2).value=='集团/多单位管理']
assert len(group_rows)==1 and values['软件价格表'].cell(group_rows[0],5).value is None
errors=[]
for p in [Q,F]:
    w=load_workbook(p,data_only=True)
    for s in w:
        for row_ in s:
            for c in row_:
                if c.data_type=='e': errors.append([p.name,s.title,c.coordinate,c.value])
assert not errors,errors
for p in pdf.glob('*.pdf'):
    reader=PdfReader(p);doc=pdfium.PdfDocument(str(p))
    assert '智慧食堂' in (reader.pages[0].extract_text() or '')
    for n,page in enumerate(doc):page.render(scale=1.4).to_pil().save(pdf/(p.stem+f'-{n+1:02}.png'))
report={'status':'passed','original_two_sheet_cell_changes':changes,'quote_formula_cells_unchanged':True,
        'feature_comparison_bytes_unchanged':True,'default_software_quote_total':5000,'tier_example_for_20000':20000,
        'tier_fee_type_confirmed':'云服务器年度费用','tier_amounts_approved':False,'server_prices_in_software_total':False,'formula_errors':errors,
        'hardware_links':len(DATA['local']),'source_date':DATA['checked_date']}
(EV/'notes-validation.json').write_text(json.dumps(report,ensure_ascii=False,indent=2))
print(json.dumps(report,ensure_ascii=False))
