import { $inject, $state, AlephaError, z } from "alepha";
import {
  GitHubSecretStore,
  NamingService,
  PlatformInspector,
  platformOptions,
  SecretFilterService,
  type SecretStoreProvider,
} from "alepha/cli/platform-lib";
import { $command, EnvUtils } from "alepha/command";
import { $logger, ConsoleColorProvider } from "alepha/logger";

export class SecretsCommand {
  protected readonly log = $logger();
  protected readonly options = $state(platformOptions);
  protected readonly inspector = $inject(PlatformInspector);
  protected readonly naming = $inject(NamingService);
  protected readonly envUtils = $inject(EnvUtils);
  protected readonly githubStore = $inject(GitHubSecretStore);
  protected readonly filter = $inject(SecretFilterService);
  protected readonly color = $inject(ConsoleColorProvider);

  protected readonly envFlags = z.object({
    env: z
      .text({
        aliases: ["e"],
        description: "Target environment",
      })
      .optional(),
  });

  // -----------------------------------------------------------------------
  // alepha p secrets list
  // -----------------------------------------------------------------------

  protected readonly list = $command({
    name: "list",
    aliases: ["ls"],
    description:
      "List secret names stored in the remote store for an environment (values are never returned). Use --format=gha to emit a ready-to-paste GitHub Actions env: block.",
    flags: z.object({
      env: z
        .text({
          aliases: ["e"],
          description: "Target environment",
        })
        .optional(),
      format: z
        .text({
          aliases: ["f"],
          description: "Output format: table (default), gha",
        })
        .optional(),
    }),
    handler: async ({ flags, root, run }) => {
      const config = await this.inspector.resolveConfig(root);
      const env = flags.env ?? config.defaultEnv;
      const envName = this.resolveEnvironmentName(config.project, env);
      const format = flags.format ?? "table";

      const store = this.resolveStore();
      await store.ensureAvailable();
      const remoteSecrets = await store.list(envName);

      run.end();

      if (format === "gha") {
        process.stdout.write(
          `# Generated by alepha — environment: ${envName}\n`,
        );
        process.stdout.write("env:\n");
        for (const secret of remoteSecrets) {
          const localName = this.filter.toLocalName(secret.name);
          process.stdout.write(
            `  ${localName}: \${{ secrets.${secret.name} }}\n`,
          );
        }
        return;
      }

      // table format
      const c = this.color;

      if (remoteSecrets.length === 0) {
        process.stdout.write(
          `\nNo secrets in environment ${c.set("CYAN", envName)}.\n\n`,
        );
        return;
      }

      process.stdout.write(
        `\n${c.set("GREY_LIGHT", "Environment:")} ${c.set("CYAN", envName)}\n\n`,
      );

      const maxNameLen = Math.max(
        ...remoteSecrets.map((s) => s.name.length),
        4,
      );

      process.stdout.write(
        `   ${c.set("GREY_LIGHT", "NAME".padEnd(maxNameLen + 2))}${c.set("GREY_LIGHT", "UPDATED")}\n`,
      );

      for (const secret of remoteSecrets) {
        const updated = secret.updatedAt
          ? new Date(secret.updatedAt).toLocaleString()
          : "-";
        process.stdout.write(
          `   ${c.set("CYAN", secret.name.padEnd(maxNameLen + 2))}${c.set("GREY_DARK", updated)}\n`,
        );
      }

      process.stdout.write("\n");
    },
  });

  // -----------------------------------------------------------------------
  // alepha p secrets diff
  // -----------------------------------------------------------------------

  protected readonly diff = $command({
    name: "diff",
    description:
      "Compare keys in local .env.<env> against the remote store. Shows + (only local, would be pushed), - (only remote, orphaned), = (in sync). Compares names only — values are not read from remote.",
    flags: this.envFlags,
    handler: async ({ flags, root, run }) => {
      const config = await this.inspector.resolveConfig(root);
      const env = flags.env ?? config.defaultEnv;
      const envName = this.resolveEnvironmentName(config.project, env);

      const envVars = await this.envUtils.parseEnv(root, [`.env.${env}`]);
      const filtered = this.filter.filter(envVars);
      const localKeys = new Set(
        Object.keys(filtered).map((k) => this.filter.toRemoteName(k)),
      );

      const store = this.resolveStore();
      await store.ensureAvailable();
      const remoteSecrets = await store.list(envName);
      const remoteKeys = new Set(remoteSecrets.map((s) => s.name));

      run.end();

      const c = this.color;
      const allKeys = [...new Set([...localKeys, ...remoteKeys])].sort();

      process.stdout.write(
        `\n${c.set("GREY_LIGHT", "Environment:")} ${c.set("CYAN", envName)}\n\n`,
      );

      let hasChanges = false;
      for (const key of allKeys) {
        const inLocal = localKeys.has(key);
        const inRemote = remoteKeys.has(key);

        if (inLocal && !inRemote) {
          process.stdout.write(`   ${c.set("GREEN", "+")} ${key}\n`);
          hasChanges = true;
        } else if (!inLocal && inRemote) {
          process.stdout.write(`   ${c.set("RED", "-")} ${key}\n`);
          hasChanges = true;
        } else {
          process.stdout.write(`   ${c.set("GREY_DARK", "=")} ${key}\n`);
        }
      }

      if (!hasChanges) {
        process.stdout.write(
          `   ${c.set("GREEN", "Local and remote are in sync.")}\n`,
        );
      }

      process.stdout.write("\n");
    },
  });

  // -----------------------------------------------------------------------
  // alepha p secrets apply
  // -----------------------------------------------------------------------

  protected readonly apply = $command({
    name: "apply",
    description:
      "Push secrets from local .env.<env> to the remote store (one upsert per key). Use --dry-run to preview. Existing remote keys not present locally are left untouched — this command never deletes.",
    flags: z.object({
      env: z
        .text({
          aliases: ["e"],
          description: "Target environment",
        })
        .optional(),
      "dry-run": z
        .boolean()
        .describe("Preview changes without pushing")
        .optional(),
    }),
    handler: async ({ flags, root, run }) => {
      const config = await this.inspector.resolveConfig(root);
      const env = flags.env ?? config.defaultEnv;
      const envName = this.resolveEnvironmentName(config.project, env);
      const dryRun = flags["dry-run"] ?? false;

      const envVars = await this.envUtils.parseEnv(root, [`.env.${env}`]);
      const filtered = this.filter.filter(envVars);
      const keys = Object.keys(filtered);

      if (keys.length === 0) {
        run.end();
        process.stdout.write("\nNo secrets to push.\n\n");
        return;
      }

      if (dryRun) {
        run.end();
        const c = this.color;
        process.stdout.write(
          `\n${c.set("GREY_LIGHT", "Dry run — environment:")} ${c.set("CYAN", envName)}\n\n`,
        );
        for (const key of keys) {
          process.stdout.write(`   ${c.set("GREEN", "+")} ${key}\n`);
        }
        process.stdout.write(
          `\n   ${keys.length} secret(s) would be pushed.\n\n`,
        );
        return;
      }

      const store = this.resolveStore();
      await store.ensureAvailable();
      await store.ensureEnvironment(envName);

      for (const key of keys) {
        const remoteName = this.filter.toRemoteName(key);
        await run({
          name: `push ${remoteName}`,
          handler: async () => {
            await store.set(envName, remoteName, filtered[key]);
          },
        });
      }

      run.end();
      process.stdout.write(
        `\n${keys.length} secret(s) pushed to ${envName}.\n`,
      );
    },
  });

  // -----------------------------------------------------------------------
  // Parent command
  // -----------------------------------------------------------------------

  public readonly secrets = $command({
    name: "secrets",
    aliases: ["sec"],
    description:
      "Sync secrets from .env.<env> to an external CI store (currently GitHub Actions environments via the `gh` CLI). Resolves the target environment name from `secrets.environmentPattern` (default `{project}-{env}`) and filters keys via SecretFilterService. Runtime secrets for Cloudflare Workers / Vercel are pushed separately during `alepha platform up`.",
    children: [this.list, this.diff, this.apply],
    handler: async ({ help }) => {
      help();
    },
  });

  // -----------------------------------------------------------------------
  // Helpers
  // -----------------------------------------------------------------------

  public resolveStore(): SecretStoreProvider {
    const storeName = this.options?.secrets?.store ?? "github";
    switch (storeName) {
      case "github":
        return this.githubStore;
      default:
        throw new AlephaError(
          `Unknown secret store "${storeName}". Available: github`,
        );
    }
  }

  protected resolveEnvironmentName(project: string, env: string): string {
    const pattern =
      this.options?.secrets?.environmentPattern ?? "{project}-{env}";

    return pattern
      .replace("{project}", this.naming.slugify(project))
      .replace("{env}", this.naming.slugify(env));
  }
}
