import * as plugins from '../plugins.ts';
import { logger } from '../logging.ts';
import type * as interfaces from '../../ts_interfaces/index.ts';
import type { StorageManager } from '../storage/index.ts';
import type { ConnectionManager } from './connectionmanager.ts';

const MANAGED_SECRETS_PREFIX = '/managed-secrets/';
const KEYCHAIN_PREFIX = 'keychain:';
const KEYCHAIN_ID_PREFIX = 'gitops-msecret-';
const SECRET_KEY_PREFIX = 'GITOPS_';

export class ManagedSecretsManager {
  private secrets: interfaces.data.IManagedSecretStored[] = [];

  constructor(
    private storageManager: StorageManager,
    private smartSecret: plugins.smartsecret.SmartSecret,
    private connectionManager: ConnectionManager,
  ) {}

  async init(): Promise<void> {
    await this.loadSecrets();
  }

  // ---- Storage helpers ----

  private keychainId(secretId: string): string {
    return `${KEYCHAIN_ID_PREFIX}${secretId}`;
  }

  private prefixedKey(key: string): string {
    return `${SECRET_KEY_PREFIX}${key}`;
  }

  private async loadSecrets(): Promise<void> {
    const keys = await this.storageManager.list(MANAGED_SECRETS_PREFIX);
    this.secrets = [];
    for (const key of keys) {
      const stored = await this.storageManager.getJSON<interfaces.data.IManagedSecretStored>(key);
      if (stored) {
        this.secrets.push(stored);
      }
    }
    if (this.secrets.length > 0) {
      logger.info(`Loaded ${this.secrets.length} managed secret(s)`);
    }
  }

  private async persistSecret(stored: interfaces.data.IManagedSecretStored, realValue: string): Promise<void> {
    // Store real value in keychain
    await this.smartSecret.setSecret(this.keychainId(stored.id), realValue);
    // Save JSON with sentinel
    const jsonStored = { ...stored, value: `${KEYCHAIN_PREFIX}${this.keychainId(stored.id)}` };
    await this.storageManager.setJSON(`${MANAGED_SECRETS_PREFIX}${stored.id}.json`, jsonStored);
    // Update in-memory sentinel too
    stored.value = jsonStored.value;
  }

  private async removeFromStorage(id: string): Promise<void> {
    await this.smartSecret.deleteSecret(this.keychainId(id));
    await this.storageManager.delete(`${MANAGED_SECRETS_PREFIX}${id}.json`);
  }

  private async getSecretValue(id: string): Promise<string | null> {
    return await this.smartSecret.getSecret(this.keychainId(id));
  }

  private toApiModel(stored: interfaces.data.IManagedSecretStored): interfaces.data.IManagedSecret {
    return {
      id: stored.id,
      key: stored.key,
      description: stored.description,
      targets: stored.targets,
      targetStatuses: stored.targetStatuses,
      createdAt: stored.createdAt,
      updatedAt: stored.updatedAt,
      lastPushedAt: stored.lastPushedAt,
    };
  }

  // ---- Push logic ----

  private async pushToTargets(
    stored: interfaces.data.IManagedSecretStored,
    mode: 'upsert' | 'delete',
    targetsOverride?: interfaces.data.IManagedSecretTarget[],
  ): Promise<interfaces.data.IManagedSecretTargetStatus[]> {
    const targets = targetsOverride || stored.targets;
    const value = mode === 'upsert' ? await this.getSecretValue(stored.id) : null;
    const prefixedKey = this.prefixedKey(stored.key);
    const results: interfaces.data.IManagedSecretTargetStatus[] = [];

    for (const target of targets) {
      const status: interfaces.data.IManagedSecretTargetStatus = {
        connectionId: target.connectionId,
        scope: target.scope,
        scopeId: target.scopeId,
        scopeName: target.scopeName,
        status: 'pending',
      };
      try {
        const provider = this.connectionManager.getProvider(target.connectionId);
        if (mode === 'upsert') {
          // Try update first; if it fails, create
          try {
            if (target.scope === 'project') {
              await provider.updateProjectSecret(target.scopeId, prefixedKey, value!);
            } else {
              await provider.updateGroupSecret(target.scopeId, prefixedKey, value!);
            }
          } catch {
            // Secret doesn't exist yet — create it
            if (target.scope === 'project') {
              await provider.createProjectSecret(target.scopeId, prefixedKey, value!);
            } else {
              await provider.createGroupSecret(target.scopeId, prefixedKey, value!);
            }
          }
        } else {
          // mode === 'delete'
          try {
            if (target.scope === 'project') {
              await provider.deleteProjectSecret(target.scopeId, prefixedKey);
            } else {
              await provider.deleteGroupSecret(target.scopeId, prefixedKey);
            }
          } catch {
            // Secret may not exist on target — that's fine
          }
        }
        status.status = 'success';
        status.lastPushedAt = Date.now();
      } catch (err) {
        status.status = 'error';
        status.error = err instanceof Error ? err.message : String(err);
      }
      results.push(status);
    }
    return results;
  }

  // ---- Public API ----

  async getAll(): Promise<interfaces.data.IManagedSecret[]> {
    return this.secrets.map((s) => this.toApiModel(s));
  }

  async getById(id: string): Promise<interfaces.data.IManagedSecret | null> {
    const stored = this.secrets.find((s) => s.id === id);
    return stored ? this.toApiModel(stored) : null;
  }

  async create(
    key: string,
    value: string,
    description: string | undefined,
    targets: interfaces.data.IManagedSecretTarget[],
  ): Promise<{
    managedSecret: interfaces.data.IManagedSecret;
    pushResults: interfaces.data.IManagedSecretTargetStatus[];
  }> {
    // Validate key
    if (key.toUpperCase().startsWith(SECRET_KEY_PREFIX)) {
      throw new Error(`Key must not start with ${SECRET_KEY_PREFIX} — the prefix is added automatically`);
    }
    if (this.secrets.some((s) => s.key === key)) {
      throw new Error(`A managed secret with key "${key}" already exists`);
    }

    const now = Date.now();
    const stored: interfaces.data.IManagedSecretStored = {
      id: crypto.randomUUID(),
      key,
      description,
      value: '', // will be set by persistSecret
      targets,
      targetStatuses: [],
      createdAt: now,
      updatedAt: now,
    };

    this.secrets.push(stored);
    await this.persistSecret(stored, value);

    // Push to all targets
    const pushResults = await this.pushToTargets(stored, 'upsert');
    stored.targetStatuses = pushResults;
    stored.lastPushedAt = now;
    stored.updatedAt = now;
    await this.storageManager.setJSON(`${MANAGED_SECRETS_PREFIX}${stored.id}.json`, {
      ...stored,
      value: `${KEYCHAIN_PREFIX}${this.keychainId(stored.id)}`,
    });

    logger.info(`Created managed secret "${key}" with ${targets.length} target(s)`);
    return { managedSecret: this.toApiModel(stored), pushResults };
  }

  async update(
    id: string,
    updates: {
      value?: string;
      description?: string;
      targets?: interfaces.data.IManagedSecretTarget[];
    },
  ): Promise<{
    managedSecret: interfaces.data.IManagedSecret;
    pushResults: interfaces.data.IManagedSecretTargetStatus[];
  }> {
    const stored = this.secrets.find((s) => s.id === id);
    if (!stored) throw new Error(`Managed secret not found: ${id}`);

    const now = Date.now();

    // Update value in keychain if provided
    if (updates.value !== undefined) {
      await this.smartSecret.setSecret(this.keychainId(id), updates.value);
    }

    if (updates.description !== undefined) {
      stored.description = updates.description;
    }

    // Handle target changes — delete from removed targets
    let removedTargets: interfaces.data.IManagedSecretTarget[] = [];
    if (updates.targets !== undefined) {
      const oldTargets = stored.targets;
      const newTargetKeys = new Set(
        updates.targets.map((t) => `${t.connectionId}:${t.scope}:${t.scopeId}`),
      );
      removedTargets = oldTargets.filter(
        (t) => !newTargetKeys.has(`${t.connectionId}:${t.scope}:${t.scopeId}`),
      );
      stored.targets = updates.targets;
    }

    stored.updatedAt = now;

    // Delete from removed targets
    if (removedTargets.length > 0) {
      await this.pushToTargets(stored, 'delete', removedTargets);
    }

    // Push to current targets
    const pushResults = await this.pushToTargets(stored, 'upsert');
    stored.targetStatuses = pushResults;
    stored.lastPushedAt = now;

    await this.storageManager.setJSON(`${MANAGED_SECRETS_PREFIX}${stored.id}.json`, {
      ...stored,
      value: `${KEYCHAIN_PREFIX}${this.keychainId(stored.id)}`,
    });

    logger.info(`Updated managed secret "${stored.key}"`);
    return { managedSecret: this.toApiModel(stored), pushResults };
  }

  async delete(id: string): Promise<{
    ok: boolean;
    deleteResults: interfaces.data.IManagedSecretTargetStatus[];
  }> {
    const stored = this.secrets.find((s) => s.id === id);
    if (!stored) throw new Error(`Managed secret not found: ${id}`);

    // Best-effort: remove from all targets
    const deleteResults = await this.pushToTargets(stored, 'delete');

    // Remove from local storage regardless
    const idx = this.secrets.indexOf(stored);
    this.secrets.splice(idx, 1);
    await this.removeFromStorage(id);

    logger.info(`Deleted managed secret "${stored.key}"`);
    return { ok: true, deleteResults };
  }

  async pushOne(id: string): Promise<{
    managedSecret: interfaces.data.IManagedSecret;
    pushResults: interfaces.data.IManagedSecretTargetStatus[];
  }> {
    const stored = this.secrets.find((s) => s.id === id);
    if (!stored) throw new Error(`Managed secret not found: ${id}`);

    const now = Date.now();
    const pushResults = await this.pushToTargets(stored, 'upsert');
    stored.targetStatuses = pushResults;
    stored.lastPushedAt = now;
    stored.updatedAt = now;

    await this.storageManager.setJSON(`${MANAGED_SECRETS_PREFIX}${stored.id}.json`, {
      ...stored,
      value: `${KEYCHAIN_PREFIX}${this.keychainId(stored.id)}`,
    });

    return { managedSecret: this.toApiModel(stored), pushResults };
  }

  async pushAll(): Promise<
    Array<{
      managedSecretId: string;
      key: string;
      pushResults: interfaces.data.IManagedSecretTargetStatus[];
    }>
  > {
    const results: Array<{
      managedSecretId: string;
      key: string;
      pushResults: interfaces.data.IManagedSecretTargetStatus[];
    }> = [];

    for (const stored of this.secrets) {
      const { pushResults } = await this.pushOne(stored.id);
      results.push({
        managedSecretId: stored.id,
        key: stored.key,
        pushResults,
      });
    }

    return results;
  }
}
