/**
 * Azure Blob Storage provider.
 *
 * Wraps @azure/storage-blob behind the cloud-agnostic StorageProvider interface.
 */

import type {
  BlobMeta,
  SignedUrlOptions,
  StorageBucket,
  StorageProvider,
  UploadOptions,
} from '../types.js';

export interface AzureBlobProviderConfig {
  connectionString?: string;
  accountName?: string;
  accountKey?: string;
  blobEndpoint?: string;
  publicBlobEndpoint?: string;
}

function parseConnectionString(connectionString: string): Partial<AzureBlobProviderConfig> {
  const parts = new Map<string, string>();

  for (const segment of connectionString.split(';')) {
    const [key, ...rest] = segment.split('=');
    if (!key || rest.length === 0) continue;
    parts.set(key, rest.join('='));
  }

  return {
    accountName: parts.get('AccountName'),
    accountKey: parts.get('AccountKey'),
    blobEndpoint: parts.get('BlobEndpoint'),
  };
}

export class AzureBlobStorageProvider implements StorageProvider {
  private client: unknown = null;
  private config: AzureBlobProviderConfig;
  private buckets = new Map<string, AzureBlobBucket>();

  constructor(config?: AzureBlobProviderConfig) {
    const envConfig = config ?? {
      connectionString: process.env.AZURE_BLOB_CONNECTION_STRING,
      accountName: process.env.AZURE_BLOB_ACCOUNT_NAME,
      accountKey: process.env.AZURE_BLOB_ACCOUNT_KEY,
      publicBlobEndpoint: process.env.AZURE_BLOB_PUBLIC_ENDPOINT,
    };

    const parsed = envConfig.connectionString
      ? parseConnectionString(envConfig.connectionString)
      : undefined;

    this.config = {
      ...parsed,
      ...envConfig,
      accountName: envConfig.accountName ?? parsed?.accountName,
      accountKey: envConfig.accountKey ?? parsed?.accountKey,
      blobEndpoint: envConfig.blobEndpoint ?? parsed?.blobEndpoint,
    };
  }

  private async getClient() {
    if (!this.client) {
      const { BlobServiceClient } = await import('@azure/storage-blob');
      if (this.config.connectionString) {
        this.client = BlobServiceClient.fromConnectionString(this.config.connectionString);
      } else if (this.config.accountName && this.config.accountKey) {
        const { StorageSharedKeyCredential } = await import('@azure/storage-blob');
        const cred = new StorageSharedKeyCredential(
          this.config.accountName,
          this.config.accountKey
        );
        const endpoint =
          this.config.blobEndpoint ?? `https://${this.config.accountName}.blob.core.windows.net`;
        this.client = new BlobServiceClient(endpoint, cred);
      } else {
        throw new Error(
          'AzureBlobStorageProvider requires AZURE_BLOB_CONNECTION_STRING or AZURE_BLOB_ACCOUNT_NAME + AZURE_BLOB_ACCOUNT_KEY'
        );
      }
    }
    return this.client as import('@azure/storage-blob').BlobServiceClient;
  }

  getBucket(name: string): StorageBucket {
    let bucket = this.buckets.get(name);
    if (!bucket) {
      bucket = new AzureBlobBucket(name, () => this.getClient(), this.config);
      this.buckets.set(name, bucket);
    }
    return bucket;
  }

  async isHealthy(): Promise<boolean> {
    try {
      const client = await this.getClient();
      // List one container to verify connectivity
      const iter = client.listContainers();
      await iter.next();
      return true;
    } catch {
      return false;
    }
  }
}

class AzureBlobBucket implements StorageBucket {
  constructor(
    private containerName: string,
    private getClient: () => Promise<import('@azure/storage-blob').BlobServiceClient>,
    private config: AzureBlobProviderConfig
  ) {}

  private async containerClient() {
    const client = await this.getClient();
    const container = client.getContainerClient(this.containerName);
    await container.createIfNotExists();
    return container;
  }

  async upload(
    key: string,
    data: Buffer | Uint8Array | string,
    options?: UploadOptions
  ): Promise<BlobMeta> {
    const container = await this.containerClient();
    const blockBlob = container.getBlockBlobClient(key);
    const buf = typeof data === 'string' ? Buffer.from(data) : Buffer.from(data);
    await blockBlob.upload(buf, buf.length, {
      blobHTTPHeaders: { blobContentType: options?.contentType },
      metadata: options?.metadata,
    });
    return {
      key,
      size: buf.length,
      contentType: options?.contentType,
      lastModified: new Date(),
      metadata: options?.metadata,
    };
  }

  async download(key: string): Promise<Buffer> {
    const container = await this.containerClient();
    const blob = container.getBlobClient(key);
    const response = await blob.downloadToBuffer();
    return response;
  }

  async delete(key: string): Promise<void> {
    const container = await this.containerClient();
    const blob = container.getBlobClient(key);
    await blob.deleteIfExists();
  }

  async exists(key: string): Promise<boolean> {
    const container = await this.containerClient();
    const blob = container.getBlobClient(key);
    return blob.exists();
  }

  async list(prefix?: string): Promise<BlobMeta[]> {
    const container = await this.containerClient();
    const results: BlobMeta[] = [];
    for await (const blob of container.listBlobsFlat({ prefix: prefix ?? undefined })) {
      results.push({
        key: blob.name,
        size: blob.properties.contentLength ?? undefined,
        contentType: blob.properties.contentType ?? undefined,
        lastModified: blob.properties.lastModified,
      });
    }
    return results;
  }

  async getSignedUrl(key: string, options?: SignedUrlOptions): Promise<string> {
    const { generateBlobSASQueryParameters, BlobSASPermissions, StorageSharedKeyCredential } =
      await import('@azure/storage-blob');

    if (!this.config.accountName || !this.config.accountKey) {
      throw new Error('Signed URLs require accountName + accountKey');
    }

    const cred = new StorageSharedKeyCredential(this.config.accountName, this.config.accountKey);
    const expiresOn = new Date(Date.now() + (options?.expiresIn ?? 3600) * 1000);
    const permissions = BlobSASPermissions.parse(options?.permissions === 'write' ? 'w' : 'r');

    const sas = generateBlobSASQueryParameters(
      {
        containerName: this.containerName,
        blobName: key,
        permissions,
        expiresOn,
      },
      cred
    );

    const baseUrl =
      this.config.publicBlobEndpoint ??
      this.config.blobEndpoint ??
      `https://${this.config.accountName}.blob.core.windows.net`;

    return `${baseUrl.replace(/\/$/, '')}/${this.containerName}/${key}?${sas.toString()}`;
  }
}
