import { BadRequestException, Body, Controller, Get, Headers, Param, Put } from '@nestjs/common';
import { SessionVerifier } from '../platform/session.verifier';
import { ActorResolver, type MessagePrincipal } from '../identity/actor.resolver';
import { ProviderCredentialService } from './provider-credential.service';

const SUPPORTED = new Set(['TWILIO_SMS', 'SMTP']);

const str = (v: unknown): string => (typeof v === 'string' ? v.trim() : '');
const isEmail = (v: string): boolean => /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(v);

/** Validate + shape the credential config for a provider type. Throws 400 on bad input.
 *  be-crm does the strict client-facing validation; this is IIOS's own guard. */
function buildConfig(providerType: string, body: Record<string, unknown>): Record<string, unknown> {
  if (providerType === 'TWILIO_SMS') {
    const accountSid = str(body.accountSid);
    const authToken = str(body.authToken);
    const fromNumber = str(body.fromNumber);
    if (!accountSid || !authToken || !fromNumber) throw new BadRequestException('accountSid, authToken and fromNumber are required');
    return { accountSid, authToken, fromNumber };
  }
  if (providerType === 'SMTP') {
    const host = str(body.host);
    const user = str(body.user);
    const pass = str(body.pass);
    const fromEmail = str(body.fromEmail);
    const fromName = str(body.fromName);
    if (!host || !user || !pass || !fromEmail) throw new BadRequestException('host, user, pass and fromEmail are required');
    if (!isEmail(fromEmail)) throw new BadRequestException('fromEmail must be a valid email');
    return { host, port: Number(body.port) || 587, secure: body.secure === true || body.secure === 'true', user, pass, fromEmail, ...(fromName ? { fromName } : {}) };
  }
  throw new BadRequestException(`unsupported providerType: ${providerType}`);
}

/**
 * Per-scope BYO integration credentials. The caller's attested session decides the scope, so a
 * tenant can only read/write ITS OWN provider credentials. The secret is sealed by the service;
 * GET returns a masked status (never the token). be-crm gates this behind tenant-admin policy.
 */
@Controller('v1/providers')
export class ProviderCredentialController {
  constructor(
    private readonly session: SessionVerifier,
    private readonly actors: ActorResolver,
    private readonly credentials: ProviderCredentialService,
  ) {}

  @Put(':providerType/credentials')
  async put(
    @Param('providerType') providerType: string,
    @Body() body: Record<string, unknown>,
    @Headers('authorization') authorization?: string,
  ) {
    this.assertSupported(providerType);
    const config = buildConfig(providerType, body ?? {});
    const scope = await this.actors.resolveScope(this.principal(authorization));
    await this.credentials.upsert(scope.id, providerType, config);
    return this.credentials.status(scope.id, providerType);
  }

  @Get(':providerType/credentials')
  async get(@Param('providerType') providerType: string, @Headers('authorization') authorization?: string) {
    this.assertSupported(providerType);
    const scope = await this.actors.resolveScope(this.principal(authorization));
    return this.credentials.status(scope.id, providerType);
  }

  private assertSupported(providerType: string): void {
    if (!SUPPORTED.has(providerType)) throw new BadRequestException(`unsupported providerType: ${providerType}`);
  }

  private principal(authorization?: string): MessagePrincipal {
    const token = (authorization ?? '').replace(/^Bearer\s+/i, '');
    if (!token) throw new BadRequestException('Authorization bearer token is required');
    return this.session.verify(token);
  }
}
