#!/usr/bin/env node
"use strict";

const fs = require("fs");
const path = require("path");
const BN = require("bn.js");
const {
  Connection,
  PublicKey,
  Transaction,
  sendAndConfirmTransaction,
} = require("@solana/web3.js");
const {
  getAssociatedTokenAddressSync,
  TOKEN_2022_PROGRAM_ID,
  TOKEN_PROGRAM_ID,
} = require("@solana/spl-token");
const {
  MintMaxVoteWeightSource,
  VoteThreshold,
  VoteThresholdType,
  VoteTipping,
  GovernanceConfig,
  PROGRAM_VERSION_V3,
  GoverningTokenConfigAccountArgs,
  GoverningTokenType,
  withCreateRealm,
  withDepositGoverningTokens,
  withCreateGovernance,
  withCreateNativeTreasury,
  withSetRealmAuthority,
  SetRealmAuthorityAction,
} = require("@solana/spl-governance");
const { parseEnvConfig, assertRequired, resolveConfigPath, parseCluster, nowStamp, getOperatorKeypairFromConfig, getMintPublicKeyFromConfig, ui } = require("./_common");

async function main() {
  const configPath = resolveConfigPath(process.argv[2]);
  const cfg = parseEnvConfig(configPath);
  [
    "GT_CLUSTER", "DAO_REALM_NAME", "SPL_GOVERNANCE_PROGRAM_ID", "DAO_VOTING_TIME_SEC", "DAO_APPROVAL_THRESHOLD_PERCENT"
  ].forEach((k) => assertRequired(cfg, k));

  const cluster = cfg.GT_CLUSTER;
  const connection = new Connection(parseCluster(cluster), "confirmed");
  const operator = getOperatorKeypairFromConfig(cfg);
  const governanceProgramId = new PublicKey(cfg.SPL_GOVERNANCE_PROGRAM_ID);
  const mint = getMintPublicKeyFromConfig(cfg);
  const votingTimeSec = Number(cfg.DAO_VOTING_TIME_SEC);
  const thresholdPct = Number(cfg.DAO_APPROVAL_THRESHOLD_PERCENT);
  const runsDir = path.resolve(cfg.DAO_RUNS_DIR || path.join(__dirname, "runs"));
  fs.mkdirSync(runsDir, { recursive: true });

  const mintAi = await connection.getAccountInfo(mint, "confirmed");
  if (!mintAi) throw new Error(`Governing mint not found: ${mint.toBase58()}`);
  if (!mintAi.owner.equals(TOKEN_PROGRAM_ID)) {
    throw new Error(
      `Этот CreateDAO ожидает governing mint под классическим SPL Token (${TOKEN_PROGRAM_ID.toBase58()}). ` +
      `Текущий mint owner: ${mintAi.owner.toBase58()}`
    );
  }

  const [realmPda] = PublicKey.findProgramAddressSync(
    [Buffer.from("governance"), Buffer.from(cfg.DAO_REALM_NAME, "utf8")],
    governanceProgramId
  );
  const realmExists = (await connection.getAccountInfo(realmPda)) !== null;
  if (realmExists) throw new Error(`Realm already exists: ${realmPda.toBase58()}`);

  const ownerAtaToken2022 = getAssociatedTokenAddressSync(mint, operator.publicKey, false, TOKEN_2022_PROGRAM_ID);
  const ownerAtaToken = getAssociatedTokenAddressSync(mint, operator.publicKey, false, TOKEN_PROGRAM_ID);
  let ownerAta = ownerAtaToken2022;
  let ownerAtaInfo = await connection.getAccountInfo(ownerAtaToken2022, "confirmed");
  let tokenProgramId = TOKEN_2022_PROGRAM_ID;
  if (!ownerAtaInfo) {
    ownerAta = ownerAtaToken;
    ownerAtaInfo = await connection.getAccountInfo(ownerAtaToken, "confirmed");
    tokenProgramId = TOKEN_PROGRAM_ID;
  }
  if (!ownerAtaInfo) throw new Error("Operator ATA for governing mint not found. Mint at least 1 token to operator first.");

  const programVersion = PROGRAM_VERSION_V3;
  const ixRealm = [];
  const communityTokenConfig = new GoverningTokenConfigAccountArgs({
    voterWeightAddin: undefined,
    maxVoterWeightAddin: undefined,
    tokenType: GoverningTokenType.Membership,
  });
  const realmPk = await withCreateRealm(
    ixRealm,
    governanceProgramId,
    programVersion,
    cfg.DAO_REALM_NAME,
    operator.publicKey,
    mint,
    operator.publicKey,
    undefined,
    MintMaxVoteWeightSource.FULL_SUPPLY_FRACTION,
    new BN(1),
    communityTokenConfig,
    undefined
  );
  const sigRealm = await sendAndConfirmTransaction(connection, new Transaction().add(...ixRealm), [operator], { commitment: "confirmed" });

  const ixDeposit = [];
  const tokenOwnerRecordPk = await withDepositGoverningTokens(
    ixDeposit,
    governanceProgramId,
    programVersion,
    realmPk,
    ownerAta,
    mint,
    operator.publicKey,
    operator.publicKey,
    operator.publicKey,
    new BN(1),
    true,
    tokenProgramId
  );
  const sigDeposit = await sendAndConfirmTransaction(connection, new Transaction().add(...ixDeposit), [operator], { commitment: "confirmed" });

  const governanceConfig = new GovernanceConfig({
    communityVoteThreshold: new VoteThreshold({ type: VoteThresholdType.YesVotePercentage, value: thresholdPct }),
    minCommunityTokensToCreateProposal: new BN(1),
    minInstructionHoldUpTime: 0,
    baseVotingTime: votingTimeSec,
    communityVoteTipping: VoteTipping.Early,
    minCouncilTokensToCreateProposal: new BN(0),
    councilVoteThreshold: new VoteThreshold({ type: VoteThresholdType.Disabled }),
    councilVetoVoteThreshold: new VoteThreshold({ type: VoteThresholdType.Disabled }),
    communityVetoVoteThreshold: new VoteThreshold({ type: VoteThresholdType.Disabled }),
    councilVoteTipping: VoteTipping.Disabled,
    votingCoolOffTime: 0,
    depositExemptProposalCount: 0,
  });
  const ixGov = [];
  const governancePk = await withCreateGovernance(
    ixGov,
    governanceProgramId,
    programVersion,
    realmPk,
    realmPk,
    governanceConfig,
    tokenOwnerRecordPk,
    operator.publicKey,
    operator.publicKey
  );
  const treasuryPk = await withCreateNativeTreasury(ixGov, governanceProgramId, programVersion, governancePk, operator.publicKey);
  const sigGov = await sendAndConfirmTransaction(connection, new Transaction().add(...ixGov), [operator], { commitment: "confirmed" });

  const ixRealmAuthority = [];
  withSetRealmAuthority(
    ixRealmAuthority,
    governanceProgramId,
    programVersion,
    realmPk,
    operator.publicKey,
    governancePk,
    SetRealmAuthorityAction.SetChecked
  );
  const sigSetRealmAuthority = await sendAndConfirmTransaction(connection, new Transaction().add(...ixRealmAuthority), [operator], { commitment: "confirmed" });

  const report = {
    createdAt: new Date().toISOString(),
    cluster,
    realmName: cfg.DAO_REALM_NAME,
    governanceProgramId: governanceProgramId.toBase58(),
    governingMint: mint.toBase58(),
    operator: operator.publicKey.toBase58(),
    realm: realmPk.toBase58(),
    governance: governancePk.toBase58(),
    nativeTreasury: treasuryPk.toBase58(),
    tokenOwnerRecord: tokenOwnerRecordPk.toBase58(),
    txRealm: sigRealm,
    txDeposit: sigDeposit,
    txGovernanceTreasury: sigGov,
    txSetRealmAuthority: sigSetRealmAuthority,
  };
  const reportPath = path.join(runsDir, `${nowStamp()}_create_dao.json`);
  fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));

  ui.ok("DAO created successfully / DAO успешно создан");
  ui.info(`Realm: ${realmPk.toBase58()}`);
  ui.info(`Governance PDA: ${governancePk.toBase58()}`);
  ui.info(`Treasury: ${treasuryPk.toBase58()}`);
  ui.info(`Report: ${reportPath}`);
}

main().catch((e) => {
  console.error("CreateDAO error:", e?.message || e);
  process.exit(1);
});
