package example;
import java.io.IOException;
import java.nio.file.*;
import java.util.Comparator;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.FlywayException;
import static example.Database.*;
public final class Migrations {
    private Migrations(){}
    static Flyway flyway(String url,String location){
        return Flyway.configure().dataSource(url,"lab_migrator",System.getenv("LAB_MIGRATION_PASSWORD"))
            .locations(location).cleanDisabled(true).load();
    }
    public static void migrate(){
        for(String url:new String[]{a(),b()}){var f=flyway(url,"classpath:db/migration");f.migrate();f.validate();}
    }
    public static void validate()throws Exception{
        try(var c=migrator(a())){
            check("2".equals(scalar(c,"SELECT count(*) FROM flyway_schema_history WHERE success AND version IN('1','2')")),"two applied migrations");
        }
        System.out.println("twoVersionedMigrations=true validatePassed=true");
    }
    static Path copy()throws IOException{
        Path dir=Files.createTempDirectory("da10-migrations-");
        for(String name:new String[]{"V1__orders.sql","V2__expand_label.sql"}){
            try(var in=Migrations.class.getResourceAsStream("/db/migration/"+name)){
                if(in==null)throw new IOException("Missing migration "+name);
                Files.copy(in,dir.resolve(name));
            }
        }return dir;
    }
    static void remove(Path dir)throws IOException{
        try(var paths=Files.walk(dir)){for(Path p:paths.sorted(Comparator.reverseOrder()).toList())Files.delete(p);}
    }
    public static void checksum()throws Exception{
        Path dir=copy();
        try{
            Path v1=dir.resolve("V1__orders.sql");byte[] original=Files.readAllBytes(v1);
            Files.writeString(v1,"\n-- changed applied migration\n",StandardOpenOption.APPEND);
            boolean failed=false;
            try{flyway(a(),"filesystem:"+dir).validate();}catch(FlywayException expected){failed=expected.getMessage().toLowerCase().contains("checksum");}
            check(failed,"checksum mismatch");
            Files.write(v1,original);flyway(a(),"filesystem:"+dir).validate();
            System.out.println("changedChecksumRejected=true originalRestoredValidatePassed=true");
        }finally{remove(dir);}
    }
    public static void ddl()throws Exception{
        Path dir=copy();
        try{
            Files.writeString(dir.resolve("V3__broken.sql"),"CREATE TABLE ddl_probe(id bigint);\nSELECT missing_column FROM ddl_probe;\n");
            boolean failed=false;
            try{flyway(a(),"filesystem:"+dir).migrate();}catch(FlywayException expected){failed=expected.getMessage().contains("missing_column");}
            try(var c=migrator(a())){
                check(failed && scalar(c,"SELECT to_regclass('public.ddl_probe')") == null,"transactional DDL rollback");
                check("0".equals(scalar(c,"SELECT count(*) FROM flyway_schema_history WHERE version='3'")),"no successful V3");
            }
            flyway(a(),"classpath:db/migration").validate();
            System.out.println("badMigrationRejected=true createdTableRolledBack=true historyV3Rows=0");
        }finally{remove(dir);}
    }
    public static void permissions()throws Exception{
        boolean denied=false;
        try(var c=app(a())){try{sql(c,"ALTER TABLE tenant_order ADD COLUMN forbidden integer");}catch(java.sql.SQLException expected){denied="42501".equals(expected.getSQLState());}}
        check(denied,"application DDL denied");
        try(var c=app(a())){check("f".equals(scalar(c,"SELECT has_table_privilege(current_user,'flyway_schema_history','UPDATE')")),"history not writable by application");}
        try(var c=migrator(a())){sql(c,"ALTER TABLE tenant_order ADD COLUMN permission_probe integer");sql(c,"ALTER TABLE tenant_order DROP COLUMN permission_probe");}
        System.out.println("applicationDdlDenied=true sqlState=42501 migrationRoleDdlAllowed=true applicationHistoryWriteDenied=true");
    }
    public static void backfill()throws Exception{
        try(var c=app(a())){
            sql(c,"DELETE FROM tenant_order");
            try{
                sql(c,"INSERT INTO tenant_order VALUES(1,7,'old-1',NULL),(2,7,'old-2',NULL),(3,7,'old-3','online-new')");
                int changed;
                try(var s=c.createStatement()){changed=s.executeUpdate("WITH batch AS (SELECT id FROM tenant_order WHERE new_label IS NULL ORDER BY id LIMIT 2) UPDATE tenant_order o SET new_label=o.legacy_label FROM batch b WHERE o.id=b.id AND o.new_label IS NULL");}
                check(changed==2&&"0".equals(scalar(c,"SELECT count(*) FROM tenant_order WHERE new_label IS NULL")),"backfill missing rows");
                check("online-new".equals(scalar(c,"SELECT new_label FROM tenant_order WHERE id=3")),"preserve online value");
                System.out.println("boundedBackfillRows=2 nullRows=0 onlineValuePreserved=true");
            }finally{sql(c,"DELETE FROM tenant_order");}
        }
    }
}
