#define _POSIX_C_SOURCE 200809L
#include "macoraone.h"
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#ifndef PATH_MAX
#define PATH_MAX 4096
#endif
#define RELEASES_API "https://api.github.com/repos/dominexmacedon-docs/greenServeFE-/releases?per_page=100"
#define MAX_RELEASES 1024
typedef struct { char name[128], version[128], tag[256], asset[256], url[2048]; int engine; } Release;
static const char *home_dir(void){const char*h=getenv("HOME");return h&&*h?h:".";}
static void quote(const char*s,char*d,size_t n){size_t u=0;if(!n)return;d[u++]='\'';while(s&&*s&&u+5<n){if(*s=='\''){memcpy(d+u,"'\\''",4);u+=4;}else d[u++]=*s;s++;}if(u+1<n)d[u++]='\'';d[u]=0;}
static int run(const char*c){printf("> %s\n",c);fflush(stdout);return system(c)==0?0:-1;}
static int run_quiet(const char*c){fflush(stdout);return system(c)==0?0:-1;}
static void progress_label(const char*phase,const char*name,const char*version){printf("\033[1;36m[%s]\033[0m \033[1;35m%s\033[0m \033[1;33m%s\033[0m\n",phase,name,version);fflush(stdout);}
static int goodver(const char*v){const unsigned char*p;if(!v||!*v||strlen(v)>64)return 0;for(p=(const unsigned char*)v;*p;p++)if(!(isalnum(*p)||*p=='.'||*p=='-'||*p=='+'))return 0;return 1;}
static void manifest_path(char*p,size_t n){snprintf(p,n,"%s/.macoraone/packages.tsv",home_dir());}
static int getinstalled(const char*n,char*v,size_t cap){char p[PATH_MAX],line[512];FILE*f;manifest_path(p,sizeof(p));f=fopen(p,"r");if(!f)return 0;while(fgets(line,sizeof(line),f)){char*t=strchr(line,'\t');if(!t)continue;*t++=0;t[strcspn(t,"\r\n")]=0;if(!strcmp(line,n)){snprintf(v,cap,"%s",t);fclose(f);return 1;}}fclose(f);return 0;}
static int setinstalled(const char*n,const char*v,int remove_it){char p[PATH_MAX],tmp[PATH_MAX],dir[PATH_MAX],line[512];FILE*in,*out;snprintf(dir,sizeof(dir),"%s/.macoraone",home_dir());if(mkdir(dir,0755)&&access(dir,F_OK))return -1;snprintf(p,sizeof(p),"%s/.macoraone/packages.tsv",home_dir());snprintf(tmp,sizeof(tmp),"%s/.macoraone/packages.tmp",home_dir());in=fopen(p,"r");out=fopen(tmp,"w");if(!out){if(in)fclose(in);return -1;}if(in){while(fgets(line,sizeof(line),in)){char c[512],*t;snprintf(c,sizeof(c),"%s",line);t=strchr(c,'\t');if(t)*t=0;if(strcmp(c,n))fputs(line,out);}fclose(in);}if(!remove_it)fprintf(out,"%s\t%s\n",n,v);if(fclose(out)||rename(tmp,p)){remove(tmp);return -1;}return 0;}
/* Discover every published Linux x86_64 package from release assets. No package names are compiled into MacoraOne. Asset stems define the installed package name; hyphens become underscores. */
static int load_catalogue(const char*dir,Release*out,size_t cap){char json[PATH_MAX],tsv[PATH_MAX],qjson[PATH_MAX*2],qtsv[PATH_MAX*2],cmd[PATH_MAX*6],line[4608];FILE*f;size_t n=0;snprintf(json,sizeof(json),"%s/releases.json",dir);snprintf(tsv,sizeof(tsv),"%s/catalogue.tsv",dir);quote(json,qjson,sizeof(qjson));quote(tsv,qtsv,sizeof(qtsv));snprintf(cmd,sizeof(cmd),"curl -fsSL --retry 3 '%s' -o %s",RELEASES_API,qjson);if(run(cmd))return -1;
/* Derive public package identity from each matching release ZIP asset, not from a compiled package table. */
snprintf(cmd,sizeof(cmd),"jq -r '.[] as $r | ($r.tag_name | try capture(\"^(?<prefix>.+)-v(?<version>.+)$\") catch empty) as $v | $r.assets[]? | select(.name | endswith(\"-linux-x86_64.zip\")) | [ (if .name == \"greenServeFE-linux-x86_64.zip\" then \"greenServeFE\" else (.name | sub(\"-linux-x86_64\\\\.zip$\";\"\") | sub(\"^greenServeFE-\";\"\") | gsub(\"-\";\"_\")) end), $v.version, $r.tag_name, .name, .browser_download_url ] | @tsv' %s > %s",qjson,qtsv);if(run_quiet(cmd))return -1;f=fopen(tsv,"r");if(!f)return -1;while(n<cap&&fgets(line,sizeof(line),f)){char*fields[5],*p=line;int i;for(i=0;i<5;i++){fields[i]=p;char*t=strchr(p,'\t');if(i<4){if(!t)break;*t=0;p=t+1;}else{if(t)break;fields[i][strcspn(fields[i],"\r\n")]=0;}}if(i!=5)continue;if(!*fields[0]||!*fields[1]||strncmp(fields[4],"https://github.com/",19))continue;snprintf(out[n].name,sizeof(out[n].name),"%s",fields[0]);snprintf(out[n].version,sizeof(out[n].version),"%s",fields[1]);snprintf(out[n].tag,sizeof(out[n].tag),"%s",fields[2]);snprintf(out[n].asset,sizeof(out[n].asset),"%s",fields[3]);snprintf(out[n].url,sizeof(out[n].url),"%s",fields[4]);out[n].engine=!strcmp(out[n].name,"greenServeFE");n++;}fclose(f);return (int)n;}
static int fetch_catalogue(Release*out,size_t cap,char*dirbuf,size_t dircap){char temp[]="/tmp/macoraone-catalogue-XXXXXX";char*dir=mkdtemp(temp);if(!dir)return -1;snprintf(dirbuf,dircap,"%s",dir);return load_catalogue(dir,out,cap);}
static const Release*find_release(const Release*r,int n,const char*name,const char*version){int i;for(i=0;i<n;i++)if(!strcmp(r[i].name,name)&&(!version||!*version||!strcmp(r[i].version,version)))return &r[i];return NULL;}
static int archive_payload(const char*zip,const char*extract,const char*name,char*src,size_t srcn){char qzip[PATH_MAX*2],qextract[PATH_MAX*2],list[PATH_MAX],qlist[PATH_MAX*2],cmd[PATH_MAX*6],line[PATH_MAX];FILE*f;quote(zip,qzip,sizeof(qzip));quote(extract,qextract,sizeof(qextract));snprintf(list,sizeof(list),"%s/archive-list.txt",extract);quote(list,qlist,sizeof(qlist));snprintf(cmd,sizeof(cmd),"unzip -Z1 %s > %s",qzip,qlist);if(run_quiet(cmd))return -1;f=fopen(list,"r");if(!f)return -1;while(fgets(line,sizeof(line),f)){char*b=strrchr(line,'/');b=b?b+1:line;line[strcspn(line,"\r\n")]=0;b=strrchr(line,'/');b=b?b+1:line;if(!strcmp(b,name)||(!strcmp(name,"greenServeFE")&&!strcmp(b,"greenServeFE"))){snprintf(src,srcn,"%s/%s",extract,line);fclose(f);return 0;}if(!strcmp(name,"__module__")&&strlen(b)>3&&!strcmp(b+strlen(b)-3,".so")){snprintf(src,srcn,"%s/%s",extract,line);fclose(f);return 0;}}fclose(f);return -1;}
int macora_package_install(const char*name,const char*verarg){Release releases[MAX_RELEASES];char dir[PATH_MAX],old[128],ver[128],zip[PATH_MAX],extract[PATH_MAX],src[PATH_MAX],dest[PATH_MAX],qzip[PATH_MAX*2],qextract[PATH_MAX*2],qurl[4096],qsrc[PATH_MAX*2],qdest[PATH_MAX*2],cmd[PATH_MAX*8];int count;const Release*p;if(!name||!*name||strchr(name,'/')||strchr(name,'\\')){fprintf(stderr,"[ERROR] Invalid package name.\n");return 1;}if(verarg&&*verarg&&!goodver(verarg)){fprintf(stderr,"[ERROR] Invalid version; use a version such as 1.0.0.\n");return 1;}count=fetch_catalogue(releases,MAX_RELEASES,dir,sizeof(dir));if(count<0){fprintf(stderr,"[ERROR] Could not read the package catalogue from GitHub.\n");return 1;}p=find_release(releases,count,name,verarg);if(!p){fprintf(stderr,"[ERROR] Package '%s'%s%s is not published with a matching Linux x86_64 release asset.\n",name,verarg&&*verarg?" version ":"",verarg&&*verarg?verarg:"");snprintf(cmd,sizeof(cmd),"rm -rf -- '%s'",dir);run_quiet(cmd);return 1;}snprintf(ver,sizeof(ver),"%s",p->version);if(getinstalled(name,old,sizeof(old))&&!strcmp(old,ver)){printf("[OK] %s %s is already installed.\n",name,ver);goto clean_ok;}snprintf(zip,sizeof(zip),"%s/package.zip",dir);snprintf(extract,sizeof(extract),"%s/payload",dir);quote(zip,qzip,sizeof(qzip));quote(extract,qextract,sizeof(qextract));quote(p->url,qurl,sizeof(qurl));progress_label("DOWNLOAD",name,ver);snprintf(cmd,sizeof(cmd),"curl -fL --show-error --retry 3 --progress-bar %s -o %s",qurl,qzip);if(run_quiet(cmd)){fprintf(stderr,"\033[31m[ERROR]\033[0m Download failed; existing installation was not changed.\n");goto fail;}if(mkdir(extract,0755)&&access(extract,F_OK)){fprintf(stderr,"[ERROR] Cannot create extraction directory.\n");goto fail;}snprintf(cmd,sizeof(cmd),"unzip -q -o %s -d %s",qzip,qextract);if(run_quiet(cmd)){fprintf(stderr,"[ERROR] ZIP extraction failed; existing installation was not changed.\n");goto fail;}if(archive_payload(zip,extract,p->engine?"greenServeFE":"__module__",src,sizeof(src))){fprintf(stderr,"[ERROR] Release ZIP has no expected executable or .so module.\n");goto fail;}if(p->engine)snprintf(dest,sizeof(dest),"/usr/local/bin/greenServeFE");else snprintf(dest,sizeof(dest),"/usr/local/lib/modules/%s.so",name);quote(src,qsrc,sizeof(qsrc));quote(dest,qdest,sizeof(qdest));progress_label("INSTALL",name,ver);if(p->engine)snprintf(cmd,sizeof(cmd),"sudo install -d -m 755 /usr/local/bin >/dev/null && sudo install -m 755 %s %s >/dev/null",qsrc,qdest);else snprintf(cmd,sizeof(cmd),"sudo install -d -m 755 /usr/local/lib/modules >/dev/null && sudo install -m 755 %s %s >/dev/null && (echo 'export GREENSERVE_MODULE_PATH=/usr/local/lib/modules' | sudo tee /etc/profile.d/greenServeFE.sh >/dev/null) && sudo chmod 644 /etc/profile.d/greenServeFE.sh >/dev/null",qsrc,qdest);if(run_quiet(cmd)){fprintf(stderr,"\033[31m[ERROR]\033[0m System installation failed; check sudo permissions.\n");goto fail;}if(setinstalled(name,ver,0)){fprintf(stderr,"[ERROR] Installed, but could not update ~/.macoraone/packages.tsv.\n");goto fail;}printf("[OK] Installed %s %s at %s (release %s).\n",name,ver,dest,p->tag);goto clean_ok;fail:snprintf(cmd,sizeof(cmd),"rm -rf -- '%s'",dir);run_quiet(cmd);return 1;clean_ok:snprintf(cmd,sizeof(cmd),"rm -rf -- '%s'",dir);run_quiet(cmd);return 0;}
int macora_package_uninstall(const char*name){char ver[128],dest[PATH_MAX],qd[PATH_MAX*2],cmd[PATH_MAX*3];if(!name||!*name||strchr(name,'/')||strchr(name,'\\')){fprintf(stderr,"[ERROR] Invalid package name.\n");return 1;}if(!getinstalled(name,ver,sizeof(ver))){fprintf(stderr,"[ERROR] %s is not recorded as installed by MacoraOne.\n",name);return 1;}if(!strcmp(name,"greenServeFE"))snprintf(dest,sizeof(dest),"/usr/local/bin/greenServeFE");else snprintf(dest,sizeof(dest),"/usr/local/lib/modules/%s.so",name);quote(dest,qd,sizeof(qd));snprintf(cmd,sizeof(cmd),"sudo rm -f -- %s",qd);if(run_quiet(cmd)||setinstalled(name,"",1)){fprintf(stderr,"[ERROR] Could not fully uninstall %s.\n",name);return 1;}printf("[OK] Uninstalled %s %s.\n",name,ver);return 0;}
int macora_package_list(void){char p[PATH_MAX],line[512],dir[PATH_MAX],cmd[PATH_MAX*2];FILE*f;Release releases[MAX_RELEASES];int count,i,n=0;manifest_path(p,sizeof(p));puts("Installed packages:");f=fopen(p,"r");if(!f)puts("  (none)");else{while(fgets(line,sizeof(line),f)){char*t=strchr(line,'\t');if(!t)continue;*t++=0;t[strcspn(t,"\r\n")]=0;printf("  %-18s %s\n",line,t);n++;}if(!n)puts("  (none)");fclose(f);}puts("\nPackages discovered from GitHub Releases:");count=fetch_catalogue(releases,MAX_RELEASES,dir,sizeof(dir));if(count<0){puts("  (catalogue unavailable; check your internet connection)");return 0;}for(i=0;i<count;i++){int j,seen=0;for(j=0;j<i;j++)if(!strcmp(releases[i].name,releases[j].name)){seen=1;break;}if(!seen)printf("  %s\n",releases[i].name);}snprintf(cmd,sizeof(cmd),"rm -rf -- '%s'",dir);run_quiet(cmd);return 0;}
int macora_package_available(void){Release releases[MAX_RELEASES];char dir[PATH_MAX],cmd[PATH_MAX*2];int count,i;if((count=fetch_catalogue(releases,MAX_RELEASES,dir,sizeof(dir)))<0){fprintf(stderr,"[ERROR] Could not read the package catalogue from GitHub.\n");return 1;}puts("Published package versions (discovered from GitHub Releases):");for(i=0;i<count;i++){int j,seen=0;printf("  %-18s %s\n",releases[i].name,releases[i].version);for(j=0;j<i;j++)if(!strcmp(releases[j].name,releases[i].name)&&!strcmp(releases[j].version,releases[i].version)){seen=1;break;}if(seen)continue;/* output above is intentionally simple; release API order is newest first */}snprintf(cmd,sizeof(cmd),"rm -rf -- '%s'",dir);run_quiet(cmd);return 0;}
