import { BadRequestException, Injectable } from '@nestjs/common';
import { AuditService } from '../../common/audit/audit.service';
import { RbacService } from '../../common/rbac/rbac.service';
import { PrismaService } from '../../prisma/prisma.service';

export const GOVERNANCE_DICTIONARY_KINDS = ['task_category', 'requirement_type', 'requirement_platform', 'requirement_source'] as const;
export type GovernanceDictionaryKind = (typeof GOVERNANCE_DICTIONARY_KINDS)[number];

export interface GovernanceDictionaryInput {
  actorId: string;
  kind: GovernanceDictionaryKind;
  id?: string;
  name: string;
  code?: string | null;
  group?: string | null;
  scope?: string;
  value?: unknown;
  permissions?: string[];
}

export interface GovernanceRemoveInput {
  actorId: string;
  kind: GovernanceDictionaryKind;
  id: string;
  permissions?: string[];
}

@Injectable()
export class GovernanceService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly auditService: AuditService,
    private readonly rbacService: RbacService,
  ) {}

  list(kind: GovernanceDictionaryKind) {
    assertDictionaryKind(kind);
    if (kind === 'task_category') {
      return this.prisma.taskCategory.findMany({ orderBy: [{ group: 'asc' }, { name: 'asc' }] });
    }
    return this.prisma.governanceDictionary.findMany({
      where: { kind, deletedAt: null },
      orderBy: { name: 'asc' },
    });
  }

  async create(input: GovernanceDictionaryInput) {
    assertDictionaryKind(input.kind);
    const actorId = requireText(input.actorId, 'actorId');
    await this.assertManagePermission(actorId, input.permissions);
    const created = input.kind === 'task_category'
      ? await this.prisma.taskCategory.create({
        data: {
          name: requireText(input.name, 'name'),
          code: input.code ?? null,
          group: input.group ?? 'other',
        },
      })
      : await this.prisma.governanceDictionary.create({
        data: {
          scope: input.scope ?? 'global',
          kind: input.kind,
          name: requireText(input.name, 'name'),
          code: input.code ?? null,
          group: input.group ?? null,
          value: input.value ?? {},
        },
      });

    await this.auditService.record({
      actorId,
      action: 'governance.dictionary_created',
      resourceType: this.resourceType(input.kind),
      resourceId: created.id,
      after: created,
    });
    return created;
  }

  async update(input: GovernanceDictionaryInput & { id: string }) {
    assertDictionaryKind(input.kind);
    const actorId = requireText(input.actorId, 'actorId');
    await this.assertManagePermission(actorId, input.permissions);
    const id = requireText(input.id, 'id');
    const updated = input.kind === 'task_category'
      ? await this.prisma.taskCategory.update({
        where: { id },
        data: {
          name: requireText(input.name, 'name'),
          code: input.code ?? null,
          group: input.group ?? 'other',
        },
      })
      : await this.prisma.governanceDictionary.update({
        where: { id },
        data: {
          name: requireText(input.name, 'name'),
          code: input.code ?? null,
          group: input.group ?? null,
          value: input.value ?? {},
        },
      });

    await this.auditService.record({
      actorId,
      action: 'governance.dictionary_updated',
      resourceType: this.resourceType(input.kind),
      resourceId: id,
      after: updated,
    });
    return updated;
  }

  async remove(input: GovernanceRemoveInput) {
    assertDictionaryKind(input.kind);
    const actorId = requireText(input.actorId, 'actorId');
    await this.assertManagePermission(actorId, input.permissions);
    const id = requireText(input.id, 'id');
    if (input.kind === 'task_category') {
      const [devTaskCount, testCaseCount] = await Promise.all([
        this.prisma.devTask.count({ where: { categoryId: id } }),
        this.prisma.testCase.count({ where: { categoryId: id } }),
      ]);
      if (devTaskCount + testCaseCount > 0) {
        throw new BadRequestException('Dictionary item is in use and cannot be hard deleted');
      }
      const removed = await this.prisma.taskCategory.delete({ where: { id } });
      await this.auditService.record({
        actorId,
        action: 'governance.dictionary_deleted',
        resourceType: 'task_category',
        resourceId: id,
        before: removed,
      });
      return removed;
    }

    const removed = await this.prisma.governanceDictionary.update({
      where: { id },
      data: { deletedAt: new Date() },
    });
    await this.auditService.record({
      actorId,
      action: 'governance.dictionary_deleted',
      resourceType: 'governance_dictionary',
      resourceId: id,
      after: removed,
    });
    return removed;
  }

  async exportAll() {
    const [taskCategories, dictionaries] = await Promise.all([
      this.prisma.taskCategory.findMany({ orderBy: [{ group: 'asc' }, { name: 'asc' }] }),
      this.prisma.governanceDictionary.findMany({ where: { deletedAt: null }, orderBy: [{ kind: 'asc' }, { name: 'asc' }] }),
    ]);
    return { taskCategories, dictionaries };
  }

  async importAll(actorId: string, items: GovernanceDictionaryInput[], permissions: string[] = []) {
    await this.assertManagePermission(actorId, permissions);
    const results = [];
    for (const item of items) {
      assertDictionaryKind(item.kind);
      if (item.kind === 'task_category') {
        results.push(await this.create({ ...item, actorId, permissions }));
      } else {
        const saved = await this.prisma.governanceDictionary.upsert({
          where: {
            scope_kind_name: {
              scope: item.scope ?? 'global',
              kind: item.kind,
              name: requireText(item.name, 'name'),
            },
          },
          update: {
            code: item.code ?? null,
            group: item.group ?? null,
            value: item.value ?? {},
            deletedAt: null,
          },
          create: {
            scope: item.scope ?? 'global',
            kind: item.kind,
            name: requireText(item.name, 'name'),
            code: item.code ?? null,
            group: item.group ?? null,
            value: item.value ?? {},
          },
        });
        results.push(saved);
      }
    }
    await this.auditService.record({
      actorId: requireText(actorId, 'actorId'),
      action: 'governance.dictionary_imported',
      resourceType: 'governance_dictionary',
      resourceId: 'bulk',
      after: { count: results.length },
    });
    return { count: results.length, items: results };
  }

  private resourceType(kind: GovernanceDictionaryKind) {
    return kind === 'task_category' ? 'task_category' : 'governance_dictionary';
  }

  private assertManagePermission(actorId: string, permissions: string[] = []) {
    return this.rbacService.assertGlobalPermission({
      actorId,
      permissions,
      requiredPermissions: ['governance:manage'],
    });
  }
}

function assertDictionaryKind(kind: string): asserts kind is GovernanceDictionaryKind {
  if (!GOVERNANCE_DICTIONARY_KINDS.includes(kind as GovernanceDictionaryKind)) {
    throw new BadRequestException(`Unsupported governance dictionary kind: ${kind}`);
  }
}

function requireText(value: string | undefined | null, field: string): string {
  const normalized = value?.trim();
  if (!normalized) throw new BadRequestException(`${field} is required`);
  return normalized;
}
