import fs from 'node:fs';import {expect,test,type Page}from '@playwright/test';import {adminEnv,root,recovery,keys,preset,selected,openPreset,save,screen,fresh,go,settled,refresh,denied,persist,record,blocked,type Role,type Check}from './permission-support';
process.env.PW_TEST_SCREENSHOT_NO_FONTS_READY='1';
for(const role of ['CO','BO','VE'] as Role[])test(`[${role}] 부서 필터·지갑 독립권한·기존 기능 회귀`,async({browser},info)=>{
 test.setTimeout(15*60_000);test.skip(process.env.QA_ALLOW_PERMISSION_MUTATION!=='1','BLOCKED: mutation guard missing');
 const file=`${root}/matrix/${role}.json`;const rows:Check[]=fs.existsSync(file)?JSON.parse(fs.readFileSync(file,'utf8')):[];const original=JSON.parse(fs.readFileSync(`${recovery}/${role}-baseline.json`,'utf8'));const c=await browser.newContext({storageState:adminEnv().storageState,viewport:{width:1600,height:1000}});const a=await c.newPage();let p=await fresh(browser,role);const changed=new Set(['스토어 거래조회','스토어 정산조회']);
 const result=(id:string,ok:boolean,detail:string,shots:string[])=>{for(let i=rows.length-1;i>=0;i--)if(rows[i].id===id&&rows[i].status==='BLOCKED')rows.splice(i,1);record(rows,role,'related regression',id,ok,detail,shots);persist(role,rows)};
 const toggle=async(settings:Record<string,boolean>,reason:string)=>{for(const label of Object.keys(settings))changed.add(label);await openPreset(a,role);await save(a,role,settings,reason);await refresh(p);await settled(p);await screen(p,info,`${role}-regression-refresh-${reason}`);await p.context().close();p=await fresh(browser,role);};
 try{
  await go(p,'/');await toggle({'스토어 거래조회':true,'스토어 정산조회':false},'filters');await go(p,'/transactions');
  if(role!=='VE'){
   const bs=p.locator('select').nth(0),vs=p.locator('select').nth(1);let branchOptions=await bs.locator('option').evaluateAll(os=>os.map(o=>({value:(o as HTMLOptionElement).value,text:o.textContent??''})));const branchLabel=p.locator('label').filter({has:p.getByText('영업점',{exact:true})}).first();const vendorLabel=p.locator('label').filter({has:p.getByText('가맹점',{exact:true})}).first();
   if(role==='BO')expect(await branchLabel.getByRole('button').isDisabled()).toBe(true);else await expect(branchLabel.getByRole('button')).toBeEnabled({timeout:60000});branchOptions=await bs.locator('option').evaluateAll(os=>os.map(o=>({value:(o as HTMLOptionElement).value,text:o.textContent??''})));
   if(role==='BO')expect(await branchLabel.getByRole('button').isDisabled()).toBe(true);
   else {const target=branchOptions.find(x=>x.value);if(target){await branchLabel.getByRole('button').click();await p.getByRole('option',{name:target.text,exact:true}).click();await settled(p);}}
   await expect(vendorLabel.getByRole('button')).toBeEnabled({timeout:60000});const options=await vs.locator('option').evaluateAll(os=>os.map(o=>({value:(o as HTMLOptionElement).value,text:o.textContent??''})));const vendor=options.find(x=>x.value);
   if(vendor){await vendorLabel.getByRole('button').click();await p.getByRole('option',{name:vendor.text,exact:true}).click();const response=p.waitForResponse(r=>new URL(r.url()).pathname==='/api/v1/store/transactions'&&r.request().method()==='GET');await p.getByRole('button',{name:'조회',exact:true}).click();const r=await response;const u=new URL(r.url());const payload=(await r.json()).payload;const shot=await screen(p,info,`${role}-individual-vendor`);const text=await p.locator('body').innerText();result('R05',!text.includes('정산합계')&&!/\([\d,−-]+원\)/.test(text),'실제 가맹점 필터 변경·조회 후 정산 숨김 유지',[shot]);
    const query=Object.fromEntries(u.searchParams);const scopeOk=Object.values(query).includes(vendor.value);if(role==='BO')result('R03',await branchLabel.getByRole('button').isDisabled()&&scopeOk,'영업점 선택 고정, 실제 소속 가맹점 선택 값이 조회 요청에 반영',[shot]);else {if((payload?.content??[]).length)result('R01',scopeOk,'법인 개별 영업점·가맹점 UI 선택 및 조회 범위 요청 확인',[shot]);else blocked(rows,role,'R01','개별 영업점·가맹점 선택 요청은 일치하나 조회 결과 0건으로 실제 거래·합계 대조 불가',[shot]);}
    if(role==='CO'){const branchValue=await bs.inputValue();const another=branchOptions.find(x=>x.value&&x.value!==branchValue);if(another){await branchLabel.getByRole('button').click();await p.getByRole('option',{name:another.text,exact:true}).click();await settled(p);const s=await screen(p,info,'CO-branch-change-vendor-reset');result('R02',(await vs.inputValue())==='','다른 영업점 선택 시 이전 가맹점 선택값이 전체로 초기화됨',[s]);}}
   }else blocked(rows,role,role==='CO'?'R01':'R03','선택 가능한 하위 가맹점이 없음');
  }
  await go(p,'/transfer');const transfer=await screen(p,info,`${role}-transfer-without-settlement`);const text=await p.locator('body').innerText();if(selected(original).includes('STORE_SEND_REMITTANCE')){
   if(new URL(p.url()).pathname.startsWith('/transfer')&&text.includes('입금 계좌')){result('W05',true,'정산 조회 OFF 상태에서 기존 송금 권한에 따라 실제 송금 1단계 입력 화면 이용 가능. 금전 이동은 미실행',[transfer]);}
   else blocked(rows,role,'W05','송금 시간·정산 방식 등 기존 사전조건으로 입력 화면 진입 불가',[transfer]);
  }else {const d=await denied(p,'/transfer',info,`${role}-transfer-baseline-denied`);result('W05',d.ok,'기존 송금 권한 없음. 정산 해제 후에도 기존 접근 제한 유지',[d.shot]);}
  // Source responses remain live. Only page navigation and permission checkboxes are changed.
  await toggle({'스토어 거래조회':false,'스토어 정산조회':true,'스토어 지갑내역':false,'스토어 송금하기':false},'wallet-independent-off');const wd=await denied(p,'/wallets',info,`${role}-wallet-independent-denied`);const td=await denied(p,'/transfer',info,`${role}-transfer-independent-denied`);result('W06',wd.ok&&td.ok,'정산만 ON, 지갑·송금 별도 권한 OFF 시 두 직접 주소 모두 접근 제한',[wd.shot,td.shot]);
  if(role==='VE'){
   await toggle({'스토어 거래조회':false,'스토어 정산조회':false,'스토어 지갑내역':selected(original).includes(keys['스토어 지갑내역']),'스토어 송금하기':selected(original).includes(keys['스토어 송금하기'])},'ve-other-features');
   const shots:string[]=[];let allowed=true;
   for(const [route,key]of [['/face-to-face-payment','PAYMENT'],['/link-shop-setup','LINKSHOP'],['/link-shop-orders','LINKSHOP'],['/link-payment-trackings','READ_LINK_PAYMENT']]){await go(p,route);const s=await screen(p,info,`VE-regression-${route.slice(1)}`);shots.push(s);const restricted=await p.getByText('접근 제한',{exact:true}).count()>0;if(selected(original).includes(key)){allowed&&=!restricted&&new URL(p.url()).pathname!=='/';}else allowed&&=restricted;}
   result('C06',allowed,'기존 결제·링크·주문·링크발송 권한에 따라 실제 화면 진입 확인. 결제/링크 전송은 미실행',shots);
   await toggle({'스토어 거래조회':false,'스토어 정산조회':false,'링크샵':false,'링크발송조회':false},'all-history-off');await go(p,'/');const s=await screen(p,info,'VE-all-history-off');result('N04',await p.getByRole('navigation',{name:'하단 메뉴'}).getByRole('link',{name:'내역',exact:true}).count()===0,'거래·정산·주문·링크발송 권한 모두 OFF 시 하단 내역 숨김',[s]);
  }
 }catch(error){if(/Timeout|ERR_CONNECTION|ERR_HTTP2/.test(String(error)))throw new Error(`BLOCKED: ${String(error)}`);throw error;}finally{try{await openPreset(a,role);await save(a,role,Object.fromEntries([...changed].map(n=>[n,selected(original).includes(keys[n])])),'관련 회귀 권한 복구');expect(selected(await preset(c,role))).toEqual(selected(original));await refresh(p);await settled(p);const s=await screen(p,info,`${role}-regression-restored`);record(rows,role,'regression restore',['F01','F02','F03','F04'],true,'거래·정산·지갑·송금·링크 등 전체 원본 권한 복구 및 스토어 새로고침',[s]);}finally{persist(role,rows);await p.context().close();await c.close();}}
});
