import {XMLParser,XMLValidator} from 'fast-xml-parser';
import type {Database} from '@/db/database';
import {insertIgnore} from './billing';
import {seal,unseal} from './secrets';
import {billingProfileSchema,type BillingProfile} from './invoice-profile';
export type InvoiceConfig={enabled:boolean;testKey:string;liveKey:string;prefix:string;replyTo:string;duplicateProtection:boolean;vat:'AAM'};
export const defaultInvoiceConfig:InvoiceConfig={enabled:false,testKey:'',liveKey:'',prefix:'',replyTo:'info@luiz-tech.hu',duplicateProtection:false,vat:'AAM'};
export async function invoiceConfig(db:Database){const row=await db.first<{data:string;revision:number}>('SELECT data,revision FROM invoice_settings WHERE id = ?',['szamlazz']);return {config:row?await unseal<InvoiceConfig>(row.data):defaultInvoiceConfig,revision:row?.revision||0}}
export const invoiceReady=(c:InvoiceConfig,mode:'test'|'live')=>c.enabled&&c.duplicateProtection&&!!(mode==='live'?c.liveKey:c.testKey);
export async function billingProfile(db:Database,userId:string){const row=await db.first<{data:string}>('SELECT data FROM billing_profiles WHERE user_id = ?',[userId]);return row?billingProfileSchema.parse(JSON.parse(row.data)):null}
export type InvoicePayload={buyer:BillingProfile;gross:number;description:string;paidDate:string;period?:string;prefix:string;replyTo:string};
export type InvoiceJob={id:string;user_id:string;mode:'test'|'live';payload:string;status:string;number:string|null;error:string|null;attempts:number;lock_until:number;created_at:number;updated_at:number};
export const huDate=(seconds:number)=>new Intl.DateTimeFormat('sv-SE',{timeZone:'Europe/Budapest',year:'numeric',month:'2-digit',day:'2-digit'}).format(new Date(seconds*1000));
const x=(v:unknown)=>String(v).replaceAll('&','&amp;').replaceAll('<','&lt;').replaceAll('>','&gt;').replaceAll('"','&quot;').replaceAll("'",'&apos;');
export function invoiceXml(id:string,p:InvoicePayload,key:string){const b=billingProfileSchema.parse(p.buyer);if(!Number.isSafeInteger(p.gross)||p.gross<=0)throw Error('Hibás számlaösszeg.');const money=(p.gross/100).toFixed(2);return `<?xml version="1.0" encoding="UTF-8"?><xmlszamla xmlns="http://www.szamlazz.hu/xmlszamla"><beallitasok><szamlaagentkulcs>${x(key)}</szamlaagentkulcs><eszamla>true</eszamla><szamlaLetoltes>false</szamlaLetoltes><valaszVerzio>2</valaszVerzio><szamlaKulsoAzon>${x(id)}</szamlaKulsoAzon></beallitasok><fejlec><teljesitesDatum>${x(p.paidDate)}</teljesitesDatum><fizetesiHataridoDatum>${x(p.paidDate)}</fizetesiHataridoDatum><fizmod>Bankkártya</fizmod><penznem>HUF</penznem><szamlaNyelve>hu</szamlaNyelve><megjegyzes>Alanyi adómentes. Stripe-fizetés rendezve.${p.period?' Időszak: '+x(p.period):''}</megjegyzes><rendelesSzam>${x(id)}</rendelesSzam><szamlaszamElotag>${x(p.prefix)}</szamlaszamElotag><fizetve>true</fizetve></fejlec><elado><emailReplyto>${x(p.replyTo)}</emailReplyto><emailTargy>Villanyrajz – számla a vásárlásodról</emailTargy><emailSzoveg>Köszönjük a vásárlást! A számla ellenértékét bankkártyával már rendezted; további fizetés nem szükséges.</emailSzoveg></elado><vevo><nev>${x(b.name)}</nev><orszag>Magyarország</orszag><irsz>${x(b.postalCode)}</irsz><telepules>${x(b.city)}</telepules><cim>${x(b.address)}</cim><email>${x(b.email)}</email><sendEmail>true</sendEmail><adoalany>${b.kind==='company'?1:-1}</adoalany>${b.kind==='company'?'<adoszam>'+x(b.taxNumber)+'</adoszam>':''}</vevo><tetelek><tetel><megnevezes>${x(p.description)}</megnevezes><mennyiseg>1</mennyiseg><mennyisegiEgyseg>db</mennyisegiEgyseg><nettoEgysegar>${money}</nettoEgysegar><afakulcs>AAM</afakulcs><nettoErtek>${money}</nettoErtek><afaErtek>0</afaErtek><bruttoErtek>${money}</bruttoErtek></tetel></tetelek></xmlszamla>`}
export function queryXml(id:string,key:string){return `<?xml version="1.0" encoding="UTF-8"?><xmlszamlaxml xmlns="http://www.szamlazz.hu/xmlszamlaxml"><szamlaagentkulcs>${x(key)}</szamlaagentkulcs><rendelesSzam>${x(id)}</rendelesSzam><pdf>false</pdf><szamlaKulsoAzon>${x(id)}</szamlaKulsoAzon></xmlszamlaxml>`}
// No provider response, customer details or credentials enter error logs.
async function agentRequest(action:string,xml:string){const form=new FormData();form.append(action,new Blob([xml],{type:'application/xml'}),'invoice.xml');const response=await fetch('https://www.szamlazz.hu/szamla/',{method:'POST',body:form,signal:AbortSignal.timeout(25000),redirect:'error'});const raw=await response.text();if(!response.ok||raw.length>2_000_000||/<!DOCTYPE|<!ENTITY/i.test(raw)||XMLValidator.validate(raw)!==true)throw Error('A számlázó válasza nem ellenőrizhető.');return new XMLParser({ignoreAttributes:true,parseTagValue:false,trimValues:true}).parse(raw)}
export async function queueInvoice(db:Database,id:string,userId:string,mode:'test'|'live',payload:InvoicePayload){await db.run(insertIgnore(db,'INSERT INTO invoice_jobs (id,user_id,mode,payload,status,number,error,attempts,lock_until,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?)'),[id,userId,mode,JSON.stringify(payload),'pending',null,null,0,0,Date.now(),Date.now()]);return processInvoice(db,id)}
export async function processInvoice(db:Database,id:string){let job=await db.first<InvoiceJob>('SELECT * FROM invoice_jobs WHERE id = ?',[id]);if(!job)throw Error('A számlázási esemény nem található.');if(job.status==='issued')return true;const {config}=await invoiceConfig(db);if(!invoiceReady(config,job.mode))return false;
 const now=Date.now();if(!await db.run('UPDATE invoice_jobs SET lock_until = ?, updated_at = ? WHERE id = ? AND lock_until < ? AND status <> ?',[now+90000,now,id,now,'issued']))throw Error('A számlázás már folyamatban van.');
 let sent=false;try{
  job=(await db.first<InvoiceJob>('SELECT * FROM invoice_jobs WHERE id = ?',[id]))!;const key=job.mode==='live'?config.liveKey:config.testKey;
  const found=await agentRequest('action-szamla_agent_xml',queryXml(id,key));
  const existing=found.szamla?.alap?.szamlaszam;
  if(typeof existing==='string'&&existing){await issued(db,id,existing);return true}
  const miss=found.xmlszamlavalasz;if(miss?.sikeres!=='false'||miss?.hibakod!=='7')throw Error('A korábbi számla ellenőrzése sikertelen.');
  // After an unknown outcome only reconciliation is allowed, never blind creation.
  if(job.status==='uncertain'||job.status==='processing')throw Error('Az előző küldés eredménye bizonytalan. Ellenőrizd a Számlázz.hu-fiókban; új számlát nem küldtünk.');
  const payload=JSON.parse(job.payload) as InvoicePayload;const xml=invoiceXml(id,payload,key);
  await db.run('UPDATE invoice_jobs SET status = ?, attempts = attempts + 1, updated_at = ? WHERE id = ?',['processing',Date.now(),id]);sent=true;
  const result=(await agentRequest('action-szamla_agent',xml)).xmlszamlavalasz;
  if(result?.sikeres==='false'){sent=false;throw Error('Számlázz.hu hibakód: '+String(result.hibakod||'ismeretlen').replace(/[^0-9]/g,'').slice(0,8)+'. Ellenőrizd a fiók beállításait és a számlázási adatokat.')}
  if(result?.sikeres!=='true'||typeof result.szamlaszam!=='string'||!result.szamlaszam)throw Error('A számlakiállítás nem igazolható.');
  await issued(db,id,result.szamlaszam);return true;
 }catch(e){const uncertain=sent||job.status==='uncertain'||job.status==='processing';const message=sent?'Bizonytalan küldési eredmény; a következő próbálkozás csak lekérdezi a számlát.':e instanceof Error?e.message:'A számlázás nem sikerült.';await db.run('UPDATE invoice_jobs SET status = ?, error = ?, lock_until = 0, updated_at = ? WHERE id = ?',[uncertain?'uncertain':'failed',message.slice(0,250),Date.now(),id]);throw Error('A számla feldolgozása még nem fejeződött be.')}
}
async function issued(db:Database,id:string,number:string){await db.run('UPDATE invoice_jobs SET status = ?, number = ?, error = NULL, lock_until = 0, updated_at = ? WHERE id = ?',['issued',number.slice(0,100),Date.now(),id])}
