import * as plugins from '../plugins.js';
import type { OneboxDatabase } from './index.js';
import type {
  IBackup,
  IBackupSchedule,
  ICertificate,
  ICertRequirement,
  IDnsRecord,
  IDomain,
  IHostedAppLifecycleRecord,
  IHostedAppLifecycleState,
  ILogEntry,
  IMetric,
  IPlatformResource,
  IPlatformService,
  IRegistry,
  IRegistryToken,
  IRetentionPolicy,
  IService,
  IServiceSecretBundleRecord,
  IServicePublishedPort,
  IServiceVolume,
  ISslCertificate,
  IStorageClass,
  IStorageTarget,
  IUser,
  TBackupScheduleScope,
  TPlatformResourceType,
  TPlatformServiceStatus,
  TPlatformServiceType,
  TStorageClassPurpose,
  TStorageTargetKind,
} from '../types.js';

export abstract class OneboxDataDoc<
  TDoc extends TData,
  TData,
> extends plugins.smartdata.SmartDataDbDoc<TDoc, TData, OneboxDatabase> {
  constructor() {
    super();
  }

  private createApiDataObject(): TData {
    const apiData: Record<string, unknown> = {};
    const saveableProperties = [
      ...(this.globalSaveableProperties || []),
      ...(this.saveableProperties || []),
    ];
    const optionsMap = (this.constructor as unknown as {
      _svDbOptions?: Record<string, { serialize?: (valueArg: unknown) => unknown }>;
    })._svDbOptions || {};

    for (const propertyName of saveableProperties) {
      const value = (this as unknown as Record<string, unknown>)[propertyName];
      const serialize = optionsMap[propertyName]?.serialize;
      apiData[propertyName] = typeof serialize === 'function' ? serialize(value) : value;
    }

    return apiData as TData;
  }

  public toData(): TData {
    return this.createApiDataObject();
  }

  public toJSON(): TData {
    return this.toData();
  }
}

export interface IMetadataRecord {
  key: string;
  value: string;
  updatedAt: number;
}

export interface ISettingRecord {
  key: string;
  value: string;
  updatedAt: number;
}

export interface ISecretSettingRecord {
  key: string;
  value: string;
  updatedAt: number;
}

export type TDnsRecordWithZone = IDnsRecord & { zoneID?: string };

@plugins.smartdata.managed()
export class MetadataDoc extends OneboxDataDoc<MetadataDoc, IMetadataRecord>
  implements IMetadataRecord {
  @plugins.smartdata.unI()
  public key!: string;

  @plugins.smartdata.svDb()
  public value!: string;

  @plugins.smartdata.svDb()
  public updatedAt!: number;
}

@plugins.smartdata.managed()
export class ServiceDoc extends OneboxDataDoc<ServiceDoc, IService> implements IService {
  @plugins.smartdata.unI()
  public id!: number;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index({ unique: true })
  public override name = '';

  @plugins.smartdata.svDb()
  public image!: string;

  @plugins.smartdata.svDb()
  public registry?: string;

  @plugins.smartdata.svDb()
  public envVars!: Record<string, string>;

  @plugins.smartdata.svDb()
  public containerArgs?: string[];

  @plugins.smartdata.svDb()
  public secretFiles?: IService['secretFiles'];

  @plugins.smartdata.svDb()
  public volumes?: IServiceVolume[];

  @plugins.smartdata.svDb()
  public publishedPorts?: IServicePublishedPort[];

  @plugins.smartdata.svDb()
  public targetPorts?: IService['targetPorts'];

  @plugins.smartdata.svDb()
  public domains?: IService['domains'];

  @plugins.smartdata.svDb()
  public publicPortMappings?: IService['publicPortMappings'];

  @plugins.smartdata.svDb()
  public port!: number;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index()
  public domain?: string;

  @plugins.smartdata.svDb()
  public containerID?: string;

  @plugins.smartdata.svDb()
  public status!: IService['status'];

  @plugins.smartdata.svDb()
  public createdAt!: number;

  @plugins.smartdata.svDb()
  public updatedAt!: number;

  @plugins.smartdata.svDb()
  public useOneboxRegistry?: boolean;

  @plugins.smartdata.svDb()
  public registryRepository?: string;

  @plugins.smartdata.svDb()
  public registryImageTag?: string;

  @plugins.smartdata.svDb()
  public autoUpdateOnPush?: boolean;

  @plugins.smartdata.svDb()
  public imageDigest?: string;

  @plugins.smartdata.svDb()
  public imageCleanup?: IService['imageCleanup'];

  @plugins.smartdata.svDb()
  public platformRequirements?: IService['platformRequirements'];

  @plugins.smartdata.svDb()
  public includeImageInBackup?: boolean;

  @plugins.smartdata.svDb()
  public appTemplateId?: string;

  @plugins.smartdata.svDb()
  public appTemplateVersion?: string;
}

@plugins.smartdata.managed()
export class HostedAppLifecycleDoc
  extends OneboxDataDoc<HostedAppLifecycleDoc, IHostedAppLifecycleRecord>
  implements IHostedAppLifecycleRecord {
  @plugins.smartdata.unI()
  public id!: number;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index({ unique: true })
  public serviceId!: number;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index({ unique: true })
  public appInstanceId!: string;

  @plugins.smartdata.svDb()
  public hostType!: 'onebox';

  @plugins.smartdata.svDb()
  public state!: IHostedAppLifecycleState;

  @plugins.smartdata.svDb()
  public createdAt!: number;

  @plugins.smartdata.svDb()
  public updatedAt!: number;
}

@plugins.smartdata.managed()
export class ServiceSecretBundleDoc
  extends OneboxDataDoc<ServiceSecretBundleDoc, IServiceSecretBundleRecord>
  implements IServiceSecretBundleRecord {
  @plugins.smartdata.unI()
  public serviceId!: number;

  @plugins.smartdata.svDb()
  public valuesEncrypted!: string;

  @plugins.smartdata.svDb()
  public createdAt!: number;

  @plugins.smartdata.svDb()
  public updatedAt!: number;
}

@plugins.smartdata.managed()
export class RegistryDoc extends OneboxDataDoc<RegistryDoc, IRegistry> implements IRegistry {
  @plugins.smartdata.unI()
  public id!: number;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index({ unique: true })
  public url!: string;

  @plugins.smartdata.svDb()
  public username!: string;

  @plugins.smartdata.svDb()
  public passwordEncrypted!: string;

  @plugins.smartdata.svDb()
  public createdAt!: number;
}

@plugins.smartdata.managed()
export class RegistryTokenDoc extends OneboxDataDoc<RegistryTokenDoc, IRegistryToken>
  implements IRegistryToken {
  @plugins.smartdata.unI()
  public id!: number;

  @plugins.smartdata.svDb()
  public override name = '';

  @plugins.smartdata.svDb()
  @plugins.smartdata.index({ unique: true })
  public tokenHash!: string;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index()
  public type!: 'global' | 'ci';

  @plugins.smartdata.svDb()
  public scope!: 'all' | string[];

  @plugins.smartdata.svDb()
  public expiresAt!: number | null;

  @plugins.smartdata.svDb()
  public createdAt!: number;

  @plugins.smartdata.svDb()
  public lastUsedAt!: number | null;

  @plugins.smartdata.svDb()
  public createdBy!: string;
}

@plugins.smartdata.managed()
export class StorageTargetDoc extends OneboxDataDoc<StorageTargetDoc, IStorageTarget>
  implements IStorageTarget {
  @plugins.smartdata.unI()
  public id!: number;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index({ unique: true })
  public override name = '';

  @plugins.smartdata.svDb()
  public displayName!: string;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index()
  public kind!: TStorageTargetKind;

  @plugins.smartdata.svDb()
  public config!: IStorageTarget['config'];

  @plugins.smartdata.svDb()
  public createdAt!: number;

  @plugins.smartdata.svDb()
  public updatedAt!: number;
}

@plugins.smartdata.managed()
export class StorageClassDoc extends OneboxDataDoc<StorageClassDoc, IStorageClass>
  implements IStorageClass {
  @plugins.smartdata.unI()
  public id!: number;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index({ unique: true })
  public override name = '';

  @plugins.smartdata.svDb()
  public displayName!: string;

  @plugins.smartdata.svDb()
  public description?: string;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index()
  public targetName!: string;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index()
  public purpose!: TStorageClassPurpose;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index()
  public isDefault?: boolean;

  @plugins.smartdata.svDb()
  public driver?: string;

  @plugins.smartdata.svDb()
  public options?: Record<string, string>;

  @plugins.smartdata.svDb()
  public createdAt!: number;

  @plugins.smartdata.svDb()
  public updatedAt!: number;
}

@plugins.smartdata.managed()
export class SettingDoc extends OneboxDataDoc<SettingDoc, ISettingRecord>
  implements ISettingRecord {
  @plugins.smartdata.unI()
  public key!: string;

  @plugins.smartdata.svDb()
  public value!: string;

  @plugins.smartdata.svDb()
  public updatedAt!: number;
}

@plugins.smartdata.managed()
export class SecretSettingDoc extends OneboxDataDoc<SecretSettingDoc, ISecretSettingRecord>
  implements ISecretSettingRecord {
  @plugins.smartdata.unI()
  public key!: string;

  @plugins.smartdata.svDb()
  public value!: string;

  @plugins.smartdata.svDb()
  public updatedAt!: number;
}

@plugins.smartdata.managed()
export class UserDoc extends OneboxDataDoc<UserDoc, IUser> implements IUser {
  @plugins.smartdata.unI()
  public id!: number;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index({ unique: true })
  public username!: string;

  @plugins.smartdata.svDb()
  public webauthnUserId!: string;

  @plugins.smartdata.svDb()
  public passkeyCredentials!: IUser['passkeyCredentials'];

  @plugins.smartdata.svDb()
  public role!: 'admin' | 'user';

  @plugins.smartdata.svDb()
  public createdAt!: number;

  @plugins.smartdata.svDb()
  public updatedAt!: number;
}

@plugins.smartdata.managed()
export class MetricDoc extends OneboxDataDoc<MetricDoc, IMetric> implements IMetric {
  @plugins.smartdata.unI()
  public id!: number;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index()
  public serviceId!: number;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index()
  public timestamp!: number;

  @plugins.smartdata.svDb()
  public cpuPercent!: number;

  @plugins.smartdata.svDb()
  public memoryUsed!: number;

  @plugins.smartdata.svDb()
  public memoryLimit!: number;

  @plugins.smartdata.svDb()
  public networkRxBytes!: number;

  @plugins.smartdata.svDb()
  public networkTxBytes!: number;
}

@plugins.smartdata.managed()
export class LogEntryDoc extends OneboxDataDoc<LogEntryDoc, ILogEntry> implements ILogEntry {
  @plugins.smartdata.unI()
  public id!: number;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index()
  public serviceId!: number;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index()
  public timestamp!: number;

  @plugins.smartdata.svDb()
  public message!: string;

  @plugins.smartdata.svDb()
  public level!: 'info' | 'warn' | 'error' | 'debug';

  @plugins.smartdata.svDb()
  public source!: 'stdout' | 'stderr';
}

@plugins.smartdata.managed()
export class DomainDoc extends OneboxDataDoc<DomainDoc, IDomain> implements IDomain {
  @plugins.smartdata.unI()
  public id!: number;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index({ unique: true })
  public domain!: string;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index()
  public dnsProvider!: 'cloudflare' | 'manual' | 'dcrouter' | null;

  @plugins.smartdata.svDb()
  public cloudflareZoneId?: string;

  @plugins.smartdata.svDb()
  public isObsolete!: boolean;

  @plugins.smartdata.svDb()
  public defaultWildcard!: boolean;

  @plugins.smartdata.svDb()
  public createdAt!: number;

  @plugins.smartdata.svDb()
  public updatedAt!: number;
}

@plugins.smartdata.managed()
export class CertificateDoc extends OneboxDataDoc<CertificateDoc, ICertificate>
  implements ICertificate {
  @plugins.smartdata.unI()
  public id!: number;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index()
  public domainId!: number;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index()
  public certDomain!: string;

  @plugins.smartdata.svDb()
  public isWildcard!: boolean;

  @plugins.smartdata.svDb()
  public certPem!: string;

  @plugins.smartdata.svDb()
  public keyPem!: string;

  @plugins.smartdata.svDb()
  public fullchainPem!: string;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index()
  public expiryDate!: number;

  @plugins.smartdata.svDb()
  public issuer!: string;

  @plugins.smartdata.svDb()
  public isValid!: boolean;

  @plugins.smartdata.svDb()
  public createdAt!: number;

  @plugins.smartdata.svDb()
  public updatedAt!: number;
}

@plugins.smartdata.managed()
export class CertRequirementDoc extends OneboxDataDoc<CertRequirementDoc, ICertRequirement>
  implements ICertRequirement {
  @plugins.smartdata.unI()
  public id!: number;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index()
  public serviceId!: number;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index()
  public domainId!: number;

  @plugins.smartdata.svDb()
  public subdomain!: string;

  @plugins.smartdata.svDb()
  public certificateId?: number;

  @plugins.smartdata.svDb()
  public status!: 'pending' | 'active' | 'renewing';

  @plugins.smartdata.svDb()
  public createdAt!: number;

  @plugins.smartdata.svDb()
  public updatedAt!: number;
}

@plugins.smartdata.managed()
export class DnsRecordDoc extends OneboxDataDoc<DnsRecordDoc, TDnsRecordWithZone>
  implements TDnsRecordWithZone {
  @plugins.smartdata.unI()
  public id!: number;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index({ unique: true })
  public domain!: string;

  @plugins.smartdata.svDb()
  public type!: 'A' | 'AAAA' | 'CNAME';

  @plugins.smartdata.svDb()
  public value!: string;

  @plugins.smartdata.svDb()
  public cloudflareID?: string;

  @plugins.smartdata.svDb()
  public zoneID?: string;

  @plugins.smartdata.svDb()
  public createdAt!: number;

  @plugins.smartdata.svDb()
  public updatedAt!: number;
}

@plugins.smartdata.managed()
export class PlatformServiceDoc extends OneboxDataDoc<PlatformServiceDoc, IPlatformService>
  implements IPlatformService {
  @plugins.smartdata.unI()
  public id!: number;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index({ unique: true })
  public override name = '';

  @plugins.smartdata.svDb()
  @plugins.smartdata.index({ unique: true })
  public type!: TPlatformServiceType;

  @plugins.smartdata.svDb()
  public status!: TPlatformServiceStatus;

  @plugins.smartdata.svDb()
  public containerId?: string;

  @plugins.smartdata.svDb()
  public config!: IPlatformService['config'];

  @plugins.smartdata.svDb()
  public adminCredentialsEncrypted?: string;

  @plugins.smartdata.svDb()
  public createdAt!: number;

  @plugins.smartdata.svDb()
  public updatedAt!: number;
}

@plugins.smartdata.managed()
export class PlatformResourceDoc extends OneboxDataDoc<PlatformResourceDoc, IPlatformResource>
  implements IPlatformResource {
  @plugins.smartdata.unI()
  public id!: number;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index()
  public platformServiceId!: number;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index()
  public serviceId!: number;

  @plugins.smartdata.svDb()
  public resourceType!: TPlatformResourceType;

  @plugins.smartdata.svDb()
  public resourceName!: string;

  @plugins.smartdata.svDb()
  public credentialsEncrypted!: string;

  @plugins.smartdata.svDb()
  public createdAt!: number;
}

@plugins.smartdata.managed()
export class BackupDoc extends OneboxDataDoc<BackupDoc, IBackup> implements IBackup {
  @plugins.smartdata.unI()
  public id!: number;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index()
  public serviceId!: number;

  @plugins.smartdata.svDb()
  public serviceName!: string;

  @plugins.smartdata.svDb()
  public filename!: string;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index({ sparse: true })
  public snapshotId?: string;

  @plugins.smartdata.svDb()
  public sizeBytes!: number;

  @plugins.smartdata.svDb()
  public storedSizeBytes?: number;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index()
  public createdAt!: number;

  @plugins.smartdata.svDb()
  public includesImage!: boolean;

  @plugins.smartdata.svDb()
  public platformResources!: TPlatformServiceType[];

  @plugins.smartdata.svDb()
  public checksum!: string;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index({ sparse: true })
  public scheduleId?: number;
}

@plugins.smartdata.managed()
export class BackupScheduleDoc extends OneboxDataDoc<BackupScheduleDoc, IBackupSchedule>
  implements IBackupSchedule {
  @plugins.smartdata.unI()
  public id!: number;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index()
  public scopeType!: TBackupScheduleScope;

  @plugins.smartdata.svDb()
  public scopePattern?: string;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index({ sparse: true })
  public serviceId?: number;

  @plugins.smartdata.svDb()
  public serviceName?: string;

  @plugins.smartdata.svDb()
  public cronExpression!: string;

  @plugins.smartdata.svDb()
  public retention!: IRetentionPolicy;

  @plugins.smartdata.svDb()
  @plugins.smartdata.index()
  public enabled!: boolean;

  @plugins.smartdata.svDb()
  public lastRunAt!: number | null;

  @plugins.smartdata.svDb()
  public nextRunAt!: number | null;

  @plugins.smartdata.svDb()
  public lastStatus!: 'success' | 'failed' | null;

  @plugins.smartdata.svDb()
  public lastError!: string | null;

  @plugins.smartdata.svDb()
  public createdAt!: number;

  @plugins.smartdata.svDb()
  public updatedAt!: number;
}

export const sslCertificateFromCertificateDoc = (docArg: CertificateDoc): ISslCertificate => ({
  id: docArg.id,
  domain: docArg.certDomain,
  certPem: docArg.certPem,
  keyPem: docArg.keyPem,
  fullchainPem: docArg.fullchainPem,
  expiryDate: docArg.expiryDate,
  issuer: docArg.issuer,
  createdAt: docArg.createdAt,
  updatedAt: docArg.updatedAt,
});
