import { Injectable, UnauthorizedException } from '@nestjs/common';
import { createHash, randomUUID, timingSafeEqual } from 'node:crypto';
import { DatabaseService } from '../database/database.service';
export type CronTaskResult={task:string;status:'COMPLETED'|'SKIPPED'|'FAILED';detail?:string;affected?:number};
@Injectable()
export class CronService { constructor(private readonly db:DatabaseService){}
 private verifySecret(value?:string){const expected=process.env.CRON_SECRET;if(!expected||!value)throw new UnauthorizedException('CRON_AUTH_REQUIRED');const a=createHash('sha256').update(value).digest(),b=createHash('sha256').update(expected).digest();if(a.length!==b.length||!timingSafeEqual(a,b))throw new UnauthorizedException('CRON_AUTH_INVALID');}
 async run(authorization?:string){const token=authorization?.startsWith('Bearer ')?authorization.slice(7):undefined;this.verifySecret(token);const requestId=randomUUID(),startedAt=new Date();if(!this.db.isConfigured())return{ok:true,configured:false,executedAt:startedAt.toISOString(),requestId,tasks:[{task:'database-dependent-tasks',status:'SKIPPED',detail:'DATABASE_NOT_CONFIGURED'} satisfies CronTaskResult]};
  const runId=randomUUID();await this.db.query(`INSERT INTO cron_runs(id,runner,status,request_id) VALUES(?,'cpanel','RUNNING',?)`,[runId,requestId]);const tasks:CronTaskResult[]=[];const execute=async(task:string,fn:()=>Promise<{affected?:number;detail?:string}>)=>{try{tasks.push({task,status:'COMPLETED',...(await fn())});}catch(error){tasks.push({task,status:'FAILED',detail:error instanceof Error?error.message.slice(0,180):'UNKNOWN_ERROR'});}};
  await execute('session-cleanup',async()=>{const r=await this.db.query(`DELETE FROM sessions WHERE expires_at<CURRENT_TIMESTAMP(3) OR (revoked_at IS NOT NULL AND revoked_at<DATE_SUB(CURRENT_TIMESTAMP(3),INTERVAL 30 DAY))`);return{affected:r.rowCount};});
  await execute('otp-cleanup',async()=>{const r=await this.db.query(`DELETE FROM otp_challenges WHERE expires_at<DATE_SUB(CURRENT_TIMESTAMP(3),INTERVAL 1 DAY) OR consumed_at<DATE_SUB(CURRENT_TIMESTAMP(3),INTERVAL 7 DAY)`);return{affected:r.rowCount};});
  await execute('document-expiry-scan',async()=>{const r=await this.db.query(`SELECT (SELECT COUNT(*) FROM license_records WHERE expiry_date BETWEEN CURRENT_DATE AND DATE_ADD(CURRENT_DATE,INTERVAL 30 DAY))+(SELECT COUNT(*) FROM project_documents WHERE expires_at BETWEEN CURRENT_TIMESTAMP(3) AND DATE_ADD(CURRENT_TIMESTAMP(3),INTERVAL 30 DAY)) due_count`);return{affected:Number(r.rows[0]?.due_count||0),detail:'Expiring items are exposed through /documents/expiring.'};});
  await execute('scheduled-report-scan',async()=>{const r=await this.db.query(`SELECT COUNT(*) count FROM report_jobs WHERE status='QUEUED'`);return{affected:Number(r.rows[0]?.count||0)};});
  await execute('farm-task-reminders',async()=>{const r=await this.db.query(`SELECT COUNT(*) count FROM farm_tasks WHERE status<>'DONE' AND due_at IS NOT NULL AND due_at<=DATE_ADD(CURRENT_TIMESTAMP(3),INTERVAL 24 HOUR)`);return{affected:Number(r.rows[0]?.count||0)};});
  await execute('pending-finance-requests',async()=>{const r=await this.db.query(`SELECT (SELECT COUNT(*) FROM deposit_requests WHERE status='PENDING')+(SELECT COUNT(*) FROM withdrawal_requests WHERE status='PENDING')+(SELECT COUNT(*) FROM investment_requests WHERE status IN ('SUBMITTED','UNDER_REVIEW')) pending_count`);return{affected:Number(r.rows[0]?.pending_count||0)};});
  await execute('health-snapshot',async()=>{await this.db.query(`INSERT INTO system_health_snapshots(id,app_status,database_status,cron_status,storage_status,details) VALUES(?,'UP','UP','RUNNING',?,?)`,[randomUUID(),process.env.PRIVATE_STORAGE_PATH?'CONFIGURED':'NOT_CONFIGURED',JSON.stringify({runner:'cpanel',requestId})]);return{affected:1};});
  const failed=tasks.some(t=>t.status==='FAILED');await this.db.query(`UPDATE cron_runs SET finished_at=CURRENT_TIMESTAMP(3),status=?,task_results=? WHERE id=?`,[failed?'PARTIAL_FAILURE':'COMPLETED',JSON.stringify(tasks),runId]);return{ok:!failed,configured:true,executedAt:startedAt.toISOString(),requestId,tasks};
 }
}
