import pino from 'pino';
import {
  ECSClient,
  ListClustersCommand,
  ListServicesCommand,
  DescribeServicesCommand,
  ListTagsForResourceCommand,
} from '@aws-sdk/client-ecs';
import {
  LambdaClient,
  ListFunctionsCommand,
  ListTagsCommand,
} from '@aws-sdk/client-lambda';
import {
  RDSClient,
  DescribeDBInstancesCommand,
  ListTagsForResourceCommand as RDSListTagsCommand,
} from '@aws-sdk/client-rds';

const logger = pino({ name: 'discovery-aws' });

export interface DiscoveredService {
  name: string;
  type: string;
  arn: string;
  region: string;
  account: string;
  tags: Record<string, string>;
  owner?: string;
  ownerSource?: 'aws-tag' | 'codeowners' | 'config' | 'heuristic';
  metadata: Record<string, any>;
  discoveredAt: Date;
}

export interface ScanResult {
  status: 'success' | 'partial_failure' | 'failed';
  discovered: number;
  errors: string[];
  services: DiscoveredService[];
}

export class AwsDiscoveryScanner {
  private ecsClient: ECSClient;
  private lambdaClient: LambdaClient;
  private rdsClient: RDSClient;
  private region: string;

  constructor(region: string, credentials?: any) {
    const config = { region, ...(credentials ? { credentials } : {}) };
    this.ecsClient = new ECSClient(config);
    this.lambdaClient = new LambdaClient(config);
    this.rdsClient = new RDSClient(config);
    this.region = region;
  }

  async scan(account: string): Promise<ScanResult> {
    const services: DiscoveredService[] = [];
    const errors: string[] = [];

    try {
      const ecsServices = await this.scanEcs(account);
      services.push(...ecsServices);
    } catch (err) {
      errors.push(`ECS scan failed: ${(err as Error).message}`);
      logger.warn({ region: this.region, error: (err as Error).message }, 'ECS scan failed');
    }

    try {
      const lambdaFns = await this.scanLambda(account);
      services.push(...lambdaFns);
    } catch (err) {
      errors.push(`Lambda scan failed: ${(err as Error).message}`);
      logger.warn({ region: this.region, error: (err as Error).message }, 'Lambda scan failed');
    }

    try {
      const rdsInstances = await this.scanRds(account);
      services.push(...rdsInstances);
    } catch (err) {
      errors.push(`RDS scan failed: ${(err as Error).message}`);
      logger.warn({ region: this.region, error: (err as Error).message }, 'RDS scan failed');
    }

    const status = errors.length === 0
      ? 'success'
      : services.length > 0
        ? 'partial_failure'
        : 'failed';

    return { status, discovered: services.length, errors, services };
  }

  private async scanEcs(account: string): Promise<DiscoveredService[]> {
    const results: DiscoveredService[] = [];

    const clusters = await this.ecsClient.send(new ListClustersCommand({}));
    for (const clusterArn of clusters.clusterArns ?? []) {
      const svcList = await this.ecsClient.send(new ListServicesCommand({ cluster: clusterArn }));
      if (!svcList.serviceArns?.length) continue;

      const described = await this.ecsClient.send(new DescribeServicesCommand({
        cluster: clusterArn,
        services: svcList.serviceArns,
      }));

      for (const svc of described.services ?? []) {
        const tags = await this.getEcsTags(svc.serviceArn!);
        const owner = tags['owner'] ?? tags['team'] ?? tags['Owner'] ?? tags['Team'];

        results.push({
          name: svc.serviceName!,
          type: 'ecs-service',
          arn: svc.serviceArn!,
          region: this.region,
          account,
          tags,
          owner,
          ownerSource: owner ? 'aws-tag' : undefined,
          metadata: {
            cluster: clusterArn.split('/').pop(),
            desiredCount: svc.desiredCount,
            runningCount: svc.runningCount,
            launchType: svc.launchType,
            taskDefinition: svc.taskDefinition,
          },
          discoveredAt: new Date(),
        });
      }
    }

    return results;
  }

  private async scanLambda(account: string): Promise<DiscoveredService[]> {
    const results: DiscoveredService[] = [];
    let marker: string | undefined;

    do {
      const response = await this.lambdaClient.send(new ListFunctionsCommand({ Marker: marker }));

      for (const fn of response.Functions ?? []) {
        let tags: Record<string, string> = {};
        try {
          const tagResponse = await this.lambdaClient.send(new ListTagsCommand({ Resource: fn.FunctionArn }));
          tags = (tagResponse.Tags ?? {}) as Record<string, string>;
        } catch {
          // Some functions may not allow tag listing
        }

        const owner = tags['owner'] ?? tags['team'] ?? tags['Owner'] ?? tags['Team'];

        results.push({
          name: fn.FunctionName!,
          type: 'lambda',
          arn: fn.FunctionArn!,
          region: this.region,
          account,
          tags,
          owner,
          ownerSource: owner ? 'aws-tag' : undefined,
          metadata: {
            runtime: fn.Runtime,
            memorySize: fn.MemorySize,
            timeout: fn.Timeout,
            lastModified: fn.LastModified,
            handler: fn.Handler,
          },
          discoveredAt: new Date(),
        });
      }

      marker = response.NextMarker;
    } while (marker);

    return results;
  }

  private async scanRds(account: string): Promise<DiscoveredService[]> {
    const results: DiscoveredService[] = [];

    const response = await this.rdsClient.send(new DescribeDBInstancesCommand({}));

    for (const db of response.DBInstances ?? []) {
      let tags: Record<string, string> = {};
      try {
        const tagResponse = await this.rdsClient.send(new RDSListTagsCommand({ ResourceName: db.DBInstanceArn }));
        tags = Object.fromEntries((tagResponse.TagList ?? []).map(t => [t.Key!, t.Value!]));
      } catch {
        // Tag listing may fail
      }

      const owner = tags['owner'] ?? tags['team'] ?? tags['Owner'] ?? tags['Team'];

      results.push({
        name: db.DBInstanceIdentifier!,
        type: `rds/${db.DBInstanceClass}`,
        arn: db.DBInstanceArn!,
        region: this.region,
        account,
        tags,
        owner,
        ownerSource: owner ? 'aws-tag' : undefined,
        metadata: {
          engine: db.Engine,
          engineVersion: db.EngineVersion,
          instanceClass: db.DBInstanceClass,
          storageType: db.StorageType,
          allocatedStorage: db.AllocatedStorage,
          multiAZ: db.MultiAZ,
          status: db.DBInstanceStatus,
        },
        discoveredAt: new Date(),
      });
    }

    return results;
  }

  private async getEcsTags(arn: string): Promise<Record<string, string>> {
    try {
      const response = await this.ecsClient.send(new ListTagsForResourceCommand({ resourceArn: arn }));
      return Object.fromEntries((response.tags ?? []).map(t => [t.key!, t.value!]));
    } catch {
      return {};
    }
  }
}
