import { TaggedError } from "better-result";

export class AuthenticationError extends TaggedError("AuthenticationError")<{
  statusCode: 401;
  message: string;
}>() {}

export class ApiError extends TaggedError("ApiError")<{
  statusCode: number;
  statusText: string;
  code?: string;
  message: string;
  hint?: string;
  traceHeaders: Record<string, string>;
}>() {}

export class MissingArgumentError extends TaggedError("MissingArgumentError")<{
  field: string;
  message: string;
}>() {
  constructor(args: { field: string }) {
    super({
      field: args.field,
      message: `Missing required argument: ${args.field}`,
    });
  }
}

export class BuildError extends TaggedError("BuildError")<{
  message: string;
  logs?: string[];
}>() {}

export class ArtifactError extends TaggedError("ArtifactError")<{
  message: string;
  cause?: unknown;
}>() {}

export class TimeoutError extends TaggedError("TimeoutError")<{
  deploymentId: string;
  elapsedMs: number;
  lastStatus: string;
  message: string;
}>() {
  constructor(args: {
    deploymentId: string;
    elapsedMs: number;
    lastStatus: string;
  }) {
    super({
      ...args,
      message: `Timed out waiting for deployment ${args.deploymentId} to reach target state (last status: ${args.lastStatus}, elapsed: ${Math.round(args.elapsedMs / 1000)}s)`,
    });
  }
}

export class DeploymentFailedError extends TaggedError(
  "DeploymentFailedError",
)<{
  deploymentId: string;
  message: string;
}>() {
  constructor(args: { deploymentId: string }) {
    super({
      deploymentId: args.deploymentId,
      message: `Deployment ${args.deploymentId} transitioned to failed status`,
    });
  }
}

export class NoExistingDeploymentError extends TaggedError(
  "NoExistingDeploymentError",
)<{
  appId: string;
  message: string;
}>() {
  constructor(args: { appId: string }) {
    super({
      appId: args.appId,
      message: `App ${args.appId} has no existing deployments. Environment-only updates require at least one prior deployment.`,
    });
  }
}

export class NoDeploymentsFoundError extends TaggedError(
  "NoDeploymentsFoundError",
)<{
  appId: string;
  message: string;
}>() {
  constructor(args: { appId: string }) {
    super({
      appId: args.appId,
      message: `App ${args.appId} has no deployments`,
    });
  }
}

export class CancelledError extends TaggedError("CancelledError")<{
  message: string;
}>() {
  constructor(args?: { message?: string }) {
    super({ message: args?.message ?? "Operation cancelled" });
  }
}

export class DestroyAggregateError extends TaggedError(
  "DestroyAggregateError",
)<{
  message: string;
  succeededDeploymentIds: string[];
  failures: Array<{ deploymentId: string; error: DeploymentOperationError }>;
  appDeleted: boolean;
}>() {
  constructor(args: {
    succeededDeploymentIds: string[];
    failures: Array<{ deploymentId: string; error: DeploymentOperationError }>;
    appDeleted: boolean;
  }) {
    const failedIds = args.failures.map((f) => f.deploymentId).join(", ");
    super({
      ...args,
      message: `Failed to destroy ${args.failures.length} deployment(s): ${failedIds}`,
    });
  }
}

export class InvalidOptionsError extends TaggedError("InvalidOptionsError")<{
  message: string;
}>() {}

export class LogStreamError extends TaggedError("LogStreamError")<{
  statusCode: number;
  message: string;
}>() {}

export type DeployError =
  | AuthenticationError
  | ApiError
  | MissingArgumentError
  | InvalidOptionsError
  | BuildError
  | ArtifactError
  | TimeoutError
  | DeploymentFailedError
  | CancelledError;

export type UpdateEnvError =
  | AuthenticationError
  | ApiError
  | MissingArgumentError
  | InvalidOptionsError
  | NoExistingDeploymentError
  | TimeoutError
  | DeploymentFailedError
  | CancelledError;

export type DestroyDeploymentError =
  | CancelledError
  | MissingArgumentError
  | NoDeploymentsFoundError
  | AuthenticationError
  | ApiError
  | TimeoutError
  | DeploymentFailedError;

export type DestroyAppError =
  | CancelledError
  | AuthenticationError
  | ApiError
  | DestroyAggregateError;

export type PromoteError =
  | AuthenticationError
  | ApiError
  | MissingArgumentError
  | NoDeploymentsFoundError
  | TimeoutError
  | DeploymentFailedError
  | CancelledError;

export type ApiRequestError = CancelledError | AuthenticationError | ApiError;

export type DeploymentOperationError =
  | CancelledError
  | AuthenticationError
  | ApiError
  | TimeoutError
  | DeploymentFailedError;
