import { Injectable } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';

export type ConsistencySeverity = 'ok' | 'warn' | 'error';
export type ConsistencyStatus = 'pass' | 'fail';

export interface ConsistencyCheckResult {
  id: string;
  label: string;
  severity: ConsistencySeverity;
  count: number;
  message: string;
}

export interface ConsistencyResult {
  generatedAt: string;
  status: ConsistencyStatus;
  counts: Record<string, number>;
  checks: {
    partitionKeys: ConsistencyCheckResult[];
    orphanReferences: ConsistencyCheckResult[];
    auditCoverage: ConsistencyCheckResult[];
  };
  summary: {
    errors: number;
    warnings: number;
    human: string;
  };
}

const COUNT_MODELS: Array<[string, string]> = [
  ['products', 'product'],
  ['projects', 'project'],
  ['versions', 'version'],
  ['requirements', 'requirement'],
  ['versionPlans', 'versionPlan'],
  ['devTasks', 'devTask'],
  ['testCases', 'testCase'],
  ['bugs', 'bug'],
  ['members', 'user'],
  ['taskCategories', 'taskCategory'],
  ['taskWorklogs', 'taskWorklog'],
  ['overtimeRecords', 'overtimeRecord'],
  ['workActivities', 'workActivity'],
  ['auditEvents', 'auditEvent'],
];

const PARTITION_KEY_CHECKS = [
  check('requirements.product_id.present', 'requirements must keep product_id partition key', 'error', "SELECT COUNT(*) AS count FROM requirements WHERE product_id IS NULL OR product_id = ''"),
  check('dev_tasks.version_id.present', 'dev_tasks must keep version_id partition key', 'error', "SELECT COUNT(*) AS count FROM dev_tasks WHERE version_id IS NULL OR version_id = ''"),
  check('test_cases.version_id.present', 'test_cases must keep version_id partition key', 'error', "SELECT COUNT(*) AS count FROM test_cases WHERE version_id IS NULL OR version_id = ''"),
  check('bugs.version_id.present', 'bugs must keep version_id partition key', 'error', "SELECT COUNT(*) AS count FROM bugs WHERE version_id IS NULL OR version_id = ''"),
  check('work_activities.created_at.present', 'work_activities must keep created_at range partition key', 'error', 'SELECT COUNT(*) AS count FROM work_activities WHERE created_at IS NULL'),
  check('task_worklogs.created_at.present', 'task_worklogs must keep created_at range partition key', 'error', 'SELECT COUNT(*) AS count FROM task_worklogs WHERE created_at IS NULL'),
  check('overtime_records.created_at.present', 'overtime_records must keep created_at range partition key', 'error', 'SELECT COUNT(*) AS count FROM overtime_records WHERE created_at IS NULL'),
  check('audit_events.created_at.present', 'audit_events must keep created_at range partition key', 'error', 'SELECT COUNT(*) AS count FROM audit_events WHERE created_at IS NULL'),
];

const ORPHAN_REFERENCE_CHECKS = [
  check('projects.product_id.exists', 'projects.product_id must reference products.id', 'error', 'SELECT COUNT(*) AS count FROM projects p LEFT JOIN products pr ON pr.id = p.product_id WHERE pr.id IS NULL'),
  check('versions.product_id.exists', 'versions.product_id must reference products.id', 'error', 'SELECT COUNT(*) AS count FROM versions v LEFT JOIN products p ON p.id = v.product_id WHERE p.id IS NULL'),
  check('versions.project_id.exists', 'versions.project_id must reference projects.id when present', 'error', 'SELECT COUNT(*) AS count FROM versions v LEFT JOIN projects p ON p.id = v.project_id WHERE v.project_id IS NOT NULL AND p.id IS NULL'),
  check('requirements.product_id.exists', 'requirements.product_id must reference products.id', 'error', 'SELECT COUNT(*) AS count FROM requirements r LEFT JOIN products p ON p.id = r.product_id WHERE p.id IS NULL'),
  check('requirements.project_id.exists', 'requirements.project_id must reference projects.id when present', 'error', 'SELECT COUNT(*) AS count FROM requirements r LEFT JOIN projects p ON p.id = r.project_id WHERE r.project_id IS NOT NULL AND p.id IS NULL'),
  check('requirements.version_id.exists', 'requirements.version_id must reference versions.id when present', 'error', 'SELECT COUNT(*) AS count /* missing_version */ FROM requirements r LEFT JOIN versions v ON v.id = r.version_id WHERE r.version_id IS NOT NULL AND v.id IS NULL'),
  check('version_plans.version_id.exists', 'version_plans.version_id must reference versions.id', 'error', 'SELECT COUNT(*) AS count FROM version_plans vp LEFT JOIN versions v ON v.id = vp.version_id WHERE v.id IS NULL'),
  check('dev_tasks.version_id.exists', 'dev_tasks.version_id must reference versions.id', 'error', 'SELECT COUNT(*) AS count FROM dev_tasks dt LEFT JOIN versions v ON v.id = dt.version_id WHERE v.id IS NULL'),
  check('dev_tasks.requirement.exists', 'dev_tasks requirement composite ref must exist when present', 'error', 'SELECT COUNT(*) AS count FROM dev_tasks dt LEFT JOIN requirements r ON r.id = dt.requirement_id AND r.product_id = dt.requirement_product_id WHERE dt.requirement_id IS NOT NULL AND r.id IS NULL'),
  check('test_cases.version_id.exists', 'test_cases.version_id must reference versions.id', 'error', 'SELECT COUNT(*) AS count FROM test_cases tc LEFT JOIN versions v ON v.id = tc.version_id WHERE v.id IS NULL'),
  check('test_cases.requirement.exists', 'test_cases requirement composite ref must exist when present', 'error', 'SELECT COUNT(*) AS count FROM test_cases tc LEFT JOIN requirements r ON r.id = tc.requirement_id AND r.product_id = tc.requirement_product_id WHERE tc.requirement_id IS NOT NULL AND r.id IS NULL'),
  check('bugs.version_id.exists', 'bugs.version_id must reference versions.id', 'error', 'SELECT COUNT(*) AS count FROM bugs b LEFT JOIN versions v ON v.id = b.version_id WHERE v.id IS NULL'),
  check('bugs.test_case.exists', 'bugs test_case composite ref must exist when present', 'error', 'SELECT COUNT(*) AS count FROM bugs b LEFT JOIN test_cases tc ON tc.id = b.test_case_id AND tc.version_id = b.test_case_version_id WHERE b.test_case_id IS NOT NULL AND tc.id IS NULL'),
];

const AUDIT_ENTITY_TYPES = [
  'product',
  'project',
  'version',
  'requirement',
  'version_plan',
  'dev_task',
  'test_case',
  'bug',
  'member',
  'task_category',
  'task_worklog',
  'overtime',
  'work_activity',
];

@Injectable()
export class ConsistencyService {
  constructor(private readonly prisma: PrismaService) {}

  async run(): Promise<ConsistencyResult> {
    const [counts, partitionKeys, orphanReferences, auditCoverage] = await Promise.all([
      this.collectCounts(),
      this.runChecks(PARTITION_KEY_CHECKS),
      this.runChecks(ORPHAN_REFERENCE_CHECKS),
      this.runAuditCoverageChecks(),
    ]);
    const allChecks = [...partitionKeys, ...orphanReferences, ...auditCoverage];
    const errors = allChecks.filter((item) => item.severity === 'error').length;
    const warnings = allChecks.filter((item) => item.severity === 'warn').length;
    const status: ConsistencyStatus = errors > 0 ? 'fail' : 'pass';

    return {
      generatedAt: new Date().toISOString(),
      status,
      counts,
      checks: { partitionKeys, orphanReferences, auditCoverage },
      summary: {
        errors,
        warnings,
        human: buildHumanSummary(status, errors, warnings, counts),
      },
    };
  }

  private async collectCounts() {
    const entries = await Promise.all(
      COUNT_MODELS.map(async ([label, model]) => [label, await (this.prisma as any)[model].count()] as const),
    );
    return Object.fromEntries(entries);
  }

  private async runChecks(checks: ConsistencyCheck[]) {
    return Promise.all(checks.map((item) => this.runSqlCheck(item)));
  }

  private async runAuditCoverageChecks() {
    return Promise.all(AUDIT_ENTITY_TYPES.map(async (entityType) => {
      const count = await this.rawCount(`SELECT COUNT(*) AS count FROM audit_events WHERE entity_type = '${entityType}'`);
      const label = `audit_events should contain mutation events for ${entityType}`;
      return {
        id: `audit.coverage.${entityType}`,
        label,
        severity: count === 0 ? 'warn' : 'ok',
        count,
        message: count === 0 ? `${label}: no events yet` : `${label}: ${count}`,
      } satisfies ConsistencyCheckResult;
    }));
  }

  private async runSqlCheck(item: ConsistencyCheck): Promise<ConsistencyCheckResult> {
    const count = await this.rawCount(item.sql);
    const severity = count > 0 ? item.severityWhenNonZero : 'ok';
    return {
      id: item.id,
      label: item.label,
      severity,
      count,
      message: count > 0 ? `${item.label}: ${count}` : `${item.label}: ok`,
    };
  }

  private async rawCount(sql: string): Promise<number> {
    const rows = await this.prisma.$queryRawUnsafe<Array<{ count: bigint | number | string }>>(sql);
    return Number(rows[0]?.count ?? 0);
  }
}

interface ConsistencyCheck {
  id: string;
  label: string;
  severityWhenNonZero: Exclude<ConsistencySeverity, 'ok'>;
  sql: string;
}

function check(
  id: string,
  label: string,
  severityWhenNonZero: Exclude<ConsistencySeverity, 'ok'>,
  sql: string,
): ConsistencyCheck {
  return { id, label, severityWhenNonZero, sql };
}

function buildHumanSummary(
  status: ConsistencyStatus,
  errors: number,
  warnings: number,
  counts: Record<string, number>,
) {
  return `V2.5 consistency ${status}: ${errors} error(s), ${warnings} warning(s), ${counts.auditEvents ?? 0} audit event(s).`;
}
