import 'server-only';

import { count, eq } from 'drizzle-orm';
import { memberships, organizations, setupRuns, users } from '@/db/schema';
import { db } from '@/lib/db';
import type { ImportReport } from './csv/import-report';
import type { CsvPreviewFileInput, CsvPreviewResult } from './csv/types';
import { readConfiguredSetupImportSource, type SetupImportSourceReport } from './setup-import-source.service';
import type { SetupStepStatus } from './setup-state.service';

export const BOOTSTRAP_ACTOR_ID = 'bootstrap-setup';

export const BOOTSTRAP_REQUIRED_TEMPLATE_FILES = [
  'users.csv',
  'organizations.csv',
  'memberships.csv',
  'crm-role-assignments.csv',
  'master-options.csv',
  'branches.csv',
  'product-types.csv',
  'document-sequences.csv',
  'approval-workflows.csv',
  'approval-steps.csv',
  'approval-matrix.csv'
] as const;

export const BOOTSTRAP_OPTIONAL_TEMPLATE_FILES = [
  'customers.csv',
  'contacts.csv',
  'leads.csv',
  'opportunities.csv',
  'quotations.csv',
  'quotation-items.csv',
  'quotation-parties.csv',
  'quotation-topics.csv',
  'quotation-topic-items.csv'
] as const;

export const BOOTSTRAP_REQUIRED_STEPS = [
  {
    stepKey: 'bootstrap_users',
    fileName: 'users.csv',
    title: 'Users',
    description: 'Create the setup administrator and required app users.',
    dependsOn: []
  },
  {
    stepKey: 'bootstrap_organizations',
    fileName: 'organizations.csv',
    title: 'Organizations',
    description: 'Create tenant organization records after users exist.',
    dependsOn: ['bootstrap_users']
  },
  {
    stepKey: 'bootstrap_memberships',
    fileName: 'memberships.csv',
    title: 'Memberships',
    description: 'Connect users to organizations with app-level roles.',
    dependsOn: ['bootstrap_users', 'bootstrap_organizations']
  },
  {
    stepKey: 'bootstrap_crm_role_assignments',
    fileName: 'crm-role-assignments.csv',
    title: 'CRM Roles',
    description: 'Assign CRM role profiles and branch/product scopes.',
    dependsOn: ['bootstrap_users', 'bootstrap_organizations', 'bootstrap_memberships']
  },
  {
    stepKey: 'bootstrap_master_options',
    fileName: 'master-options.csv',
    title: 'Master Options',
    description: 'Seed governed CRM option categories and labels.',
    dependsOn: ['bootstrap_users', 'bootstrap_organizations', 'bootstrap_memberships']
  },
  {
    stepKey: 'bootstrap_branches',
    fileName: 'branches.csv',
    title: 'Branches',
    description: 'Create branch options used for CRM scope and numbering.',
    dependsOn: [
      'bootstrap_users',
      'bootstrap_organizations',
      'bootstrap_memberships',
      'bootstrap_master_options'
    ]
  },
  {
    stepKey: 'bootstrap_product_types',
    fileName: 'product-types.csv',
    title: 'Product Types',
    description: 'Create product type options used for CRM scope and numbering.',
    dependsOn: [
      'bootstrap_users',
      'bootstrap_organizations',
      'bootstrap_memberships',
      'bootstrap_master_options'
    ]
  },
  {
    stepKey: 'bootstrap_document_sequences',
    fileName: 'document-sequences.csv',
    title: 'Document Sequences',
    description: 'Configure document numbering for CRM entities.',
    dependsOn: ['bootstrap_organizations', 'bootstrap_branches', 'bootstrap_product_types']
  },
  {
    stepKey: 'bootstrap_approval_workflows',
    fileName: 'approval-workflows.csv',
    title: 'Approval Workflows',
    description: 'Create approval workflow headers.',
    dependsOn: ['bootstrap_organizations']
  },
  {
    stepKey: 'bootstrap_approval_steps',
    fileName: 'approval-steps.csv',
    title: 'Approval Steps',
    description: 'Create ordered approval steps for each workflow.',
    dependsOn: ['bootstrap_approval_workflows']
  },
  {
    stepKey: 'bootstrap_approval_matrix',
    fileName: 'approval-matrix.csv',
    title: 'Approval Matrix',
    description: 'Create approval routing rules by scope and amount.',
    dependsOn: ['bootstrap_approval_workflows', 'bootstrap_branches', 'bootstrap_product_types']
  }
] as const;

export class BootstrapSetupError extends Error {
  status: number;
  details?: Record<string, unknown>;

  constructor(message: string, status = 400, details?: Record<string, unknown>) {
    super(message);
    this.name = 'BootstrapSetupError';
    this.status = status;
    this.details = details;
  }
}

const SENSITIVE_COLUMN_PATTERN = /(password|password_hash|token|secret)/i;

function maskSensitiveValues(values: Record<string, string>) {
  return Object.fromEntries(
    Object.entries(values).map(([key, value]) => [
      key,
      SENSITIVE_COLUMN_PATTERN.test(key) && value ? '********' : value
    ])
  );
}

export function sanitizeCsvPreviewForResponse(preview: CsvPreviewResult): CsvPreviewResult {
  return {
    ...preview,
    files: preview.files.map((file) => ({
      ...file,
      rows: file.rows.slice(0, 5).map((row) => ({
        ...row,
        values: maskSensitiveValues(row.values)
      }))
    }))
  };
}

export function sanitizeCsvPreviewForPersistence(preview: CsvPreviewResult) {
  return {
    summary: preview.summary,
    generatedAt: preview.generatedAt,
    previewHash: preview.previewHash,
    files: preview.files.map((file) => ({
      fileName: file.fileName,
      template: file.template,
      importOrder: file.importOrder,
      status: file.status,
      summary: file.summary,
      errors: file.errors,
      warnings: file.warnings
    }))
  };
}

export function sanitizeCsvPreviewFileForPersistence(preview: CsvPreviewResult, fileName: string) {
  const file = preview.files.find((candidate) => candidate.fileName.toLowerCase() === fileName.toLowerCase());
  if (!file) return sanitizeCsvPreviewForPersistence(preview);
  return {
    summary: file.summary,
    generatedAt: preview.generatedAt,
    previewHash: preview.previewHash,
    file: {
      fileName: file.fileName,
      template: file.template,
      importOrder: file.importOrder,
      status: file.status,
      summary: file.summary,
      errors: file.errors,
      warnings: file.warnings
    }
  };
}

export interface BootstrapSetupStatus {
  initialized: boolean;
  tokenRequired: boolean;
  checks: {
    completedSetupRun: boolean;
    superAdminUser: boolean;
    organization: boolean;
    adminMembership: boolean;
  };
  requiredFiles: string[];
  optionalFiles: string[];
  requiredSteps: typeof BOOTSTRAP_REQUIRED_STEPS;
  importSource: SetupImportSourceReport;
}

async function hasRows<T>(query: Promise<Array<T & { value: number }>>) {
  try {
    const [row] = await query;
    return Number(row?.value ?? 0) > 0;
  } catch {
    return false;
  }
}

export async function getBootstrapSetupStatus(): Promise<BootstrapSetupStatus> {
  const [completedSetupRun, superAdminUser, organization, adminMembership, importSource] = await Promise.all([
    hasRows(
      db
        .select({ value: count() })
        .from(setupRuns)
        .where(eq(setupRuns.status, 'completed'))
    ),
    hasRows(
      db
        .select({ value: count() })
        .from(users)
        .where(eq(users.systemRole, 'super_admin'))
    ),
    hasRows(db.select({ value: count() }).from(organizations)),
    hasRows(
      db
        .select({ value: count() })
        .from(memberships)
        .where(eq(memberships.role, 'admin'))
    ),
    readConfiguredSetupImportSource({
      requiredFiles: BOOTSTRAP_REQUIRED_TEMPLATE_FILES,
      optionalFiles: BOOTSTRAP_OPTIONAL_TEMPLATE_FILES
    }).then((result) => result.report)
  ]);

  const checks = {
    completedSetupRun,
    superAdminUser,
    organization,
    adminMembership
  };

  return {
    initialized: Object.values(checks).every(Boolean),
    tokenRequired: Boolean(process.env.BOOTSTRAP_SETUP_TOKEN?.trim()),
    checks,
    requiredFiles: [...BOOTSTRAP_REQUIRED_TEMPLATE_FILES],
    optionalFiles: [...BOOTSTRAP_OPTIONAL_TEMPLATE_FILES],
    requiredSteps: [...BOOTSTRAP_REQUIRED_STEPS],
    importSource
  };
}

export async function assertBootstrapSetupAvailable() {
  const status = await getBootstrapSetupStatus();

  if (status.initialized) {
    throw new BootstrapSetupError('Bootstrap setup is no longer available after setup completion.', 403, {
      checks: status.checks
    });
  }

  return status;
}

export function assertBootstrapToken(token: string | null | undefined) {
  const expectedToken = process.env.BOOTSTRAP_SETUP_TOKEN?.trim();

  if (!expectedToken) return;

  if (!token || token !== expectedToken) {
    throw new BootstrapSetupError('Bootstrap setup token is invalid.', 403);
  }
}

export function getMissingBootstrapRequiredFiles(files: CsvPreviewFileInput[]) {
  const uploaded = new Set(
    files
      .map((file) => file.fileName)
      .filter((fileName): fileName is string => typeof fileName === 'string')
      .map((fileName) => fileName.trim().toLowerCase())
  );
  return BOOTSTRAP_REQUIRED_TEMPLATE_FILES.filter((fileName) => !uploaded.has(fileName));
}

export function assertBootstrapRequiredFiles(files: CsvPreviewFileInput[]) {
  const missingFiles = getMissingBootstrapRequiredFiles(files);

  if (missingFiles.length > 0) {
    throw new BootstrapSetupError('Upload all required setup CSV files before running bootstrap setup.', 400, {
      missingFiles
    });
  }
}

export function assertBootstrapStepFiles(stepKey: string, files: CsvPreviewFileInput[]) {
  const step = BOOTSTRAP_REQUIRED_STEPS.find((candidate) => candidate.stepKey === stepKey);

  if (!step) {
    throw new BootstrapSetupError('Unknown bootstrap setup step.', 400, {
      stepKey,
      allowedStepKeys: BOOTSTRAP_REQUIRED_STEPS.map((candidate) => candidate.stepKey)
    });
  }

  if (files.length !== 1) {
    throw new BootstrapSetupError('Upload exactly one CSV file for this setup step.', 400, {
      stepKey,
      expectedFileName: step.fileName,
      receivedFiles: files.map((file) => file.fileName)
    });
  }

  const [file] = files;

  if (file.fileName.toLowerCase() !== step.fileName.toLowerCase()) {
    throw new BootstrapSetupError('Uploaded CSV file does not match the current setup step.', 400, {
      stepKey,
      expectedFileName: step.fileName,
      receivedFileName: file.fileName
    });
  }

  return step;
}

export function setupStepStatusFromReportStatus(status: 'PASS' | 'WARNING' | 'FAIL'): SetupStepStatus {
  if (status === 'PASS') return 'passed';
  if (status === 'WARNING') return 'warning';
  return 'failed';
}

export function setupStepStatusFromImportReport(report: ImportReport): SetupStepStatus {
  if (report.status === 'FAIL') return 'failed';
  if (report.status === 'WARNING') return 'warning';
  return 'passed';
}

export function setupStepStatusFromPreview(preview: CsvPreviewResult): SetupStepStatus {
  if (preview.summary.error > 0 || preview.files.some((file) => file.status === 'FAIL')) return 'failed';
  if (preview.summary.warning > 0 || preview.files.some((file) => file.status === 'WARNING')) return 'warning';
  return 'passed';
}

export function toSerializableRecord(value: unknown): Record<string, unknown> {
  return JSON.parse(JSON.stringify(value)) as Record<string, unknown>;
}
