import type { CsvParsedFile, CsvParsedRow, CsvPreviewError, CsvPreviewWarning } from './types';

type RowValues = Record<string, string>;

const EMAIL_COLUMNS = new Set([
  'email',
  'created_by_email',
  'updated_by_email',
  'user_email',
  'owner_user_email',
  'owner_marketing_user_email',
  'assigned_sales_owner_email',
  'assigned_to_user_email',
  'salesman_email',
  'shared_to_user_email',
  'shared_by_user_email',
  'contact_email'
]);

const BOOLEAN_COLUMNS = new Set([
  'reset_password',
  'is_primary',
  'is_active',
  'is_system',
  'is_required',
  'is_default',
  'is_hot_project',
  'is_sent'
]);

const INTEGER_COLUMNS = new Set([
  'sort_order',
  'current_number',
  'padding_length',
  'step_number',
  'sla_hours',
  'priority',
  'item_number',
  'revision',
  'chance_percent'
]);

const DECIMAL_COLUMNS = new Set([
  'estimated_value',
  'min_amount',
  'max_amount',
  'subtotal',
  'total_amount',
  'exchange_rate',
  'discount',
  'tax_rate',
  'tax_amount',
  'quantity',
  'unit_price',
  'total_price'
]);

const DATE_COLUMNS = new Set(['quotation_date', 'valid_until', 'expected_close_date']);

const ENUMS: Record<string, readonly string[]> = {
  system_role: ['super_admin', 'user'],
  membership_role: ['admin', 'user'],
  branch_scope_mode: ['all', 'selected', 'none', 'inherit'],
  product_type_scope_mode: ['all', 'selected', 'none', 'inherit'],
  entity_type: ['quotation'],
  approval_mode: ['sequential', 'parallel', 'any'],
  calendar_mode: ['calendar_days', 'business_days'],
  timeout_action: ['none', 'auto_approve', 'auto_reject', 'escalate'],
  reset_policy: ['period', 'never', 'manual'],
  discount_type: ['percent', 'amount', ''],
  outcome: ['open', 'won', 'lost', ''],
  is_active: ['true', 'false', ''],
  is_primary: ['true', 'false', ''],
  is_system: ['true', 'false', ''],
  is_required: ['true', 'false', ''],
  is_default: ['true', 'false', ''],
  reset_password: ['true', 'false', ''],
  is_hot_project: ['true', 'false', ''],
  is_sent: ['true', 'false', '']
};

function buildIssue(
  file: string,
  row: number,
  column: string,
  code: string,
  message: string,
  suggestedFix: string
): CsvPreviewError {
  return { file, row, column, code, message, suggestedFix };
}

function isValidEmail(value: string): boolean {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}

function isInteger(value: string): boolean {
  return /^-?\d+$/.test(value);
}

function isDecimal(value: string): boolean {
  return /^-?\d+(\.\d+)?$/.test(value);
}

function isDate(value: string): boolean {
  if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
  const date = new Date(`${value}T00:00:00.000Z`);
  return !Number.isNaN(date.getTime()) && date.toISOString().startsWith(value);
}

function isLowerSnake(value: string): boolean {
  return /^[a-z0-9]+(?:_[a-z0-9]+)*$/.test(value);
}

function validateCodeShape(file: CsvParsedFile, row: CsvParsedRow, errors: CsvPreviewError[]): void {
  const values = row.values;
  const lowerSnakeColumns = [
    'slug',
    'branch_code',
    'product_type_code',
    'workflow_code',
    'topic_key',
    'category',
    'code',
    'crm_role_code',
    'role_code'
  ];

  if (values.organization_code && !/^[A-Z0-9_-]+$/.test(values.organization_code)) {
    errors.push(
      buildIssue(
        file.fileName,
        row.rowNumber,
        'organization_code',
        'INVALID_FORMAT',
        'Organization code must be uppercase letters, numbers, underscore, or dash.',
        'Use values like ALLA or ALLA_DEMO.'
      )
    );
  }

  lowerSnakeColumns.forEach((column) => {
    const value = values[column];
    if (!value || column === 'code') return;
    if (!isLowerSnake(value)) {
      errors.push(
        buildIssue(
          file.fileName,
          row.rowNumber,
          column,
          'INVALID_FORMAT',
          `${column} should use lowercase snake_case.`,
          'Use lowercase letters, numbers, and underscores only.'
        )
      );
    }
  });
}

function validateJson(file: CsvParsedFile, row: CsvParsedRow, errors: CsvPreviewError[]): void {
  const value = row.values.metadata_json;
  if (!value) return;
  try {
    const parsed = JSON.parse(value) as unknown;
    if (parsed === null || Array.isArray(parsed) || typeof parsed !== 'object') {
      errors.push(
        buildIssue(
          file.fileName,
          row.rowNumber,
          'metadata_json',
          'INVALID_JSON_OBJECT',
          'metadata_json must be a JSON object.',
          'Use an object such as {} or {"prefix":"HO"}.'
        )
      );
    }
  } catch {
    errors.push(
      buildIssue(
        file.fileName,
        row.rowNumber,
        'metadata_json',
        'INVALID_JSON',
        'metadata_json is not valid JSON.',
        'Fix JSON syntax or leave the column empty.'
      )
    );
  }
}

function validateNumericalRelationships(
  file: CsvParsedFile,
  row: CsvParsedRow,
  errors: CsvPreviewError[],
  warnings: CsvPreviewWarning[]
): void {
  const minAmount = row.values.min_amount;
  const maxAmount = row.values.max_amount;
  if (minAmount && maxAmount && Number(minAmount) > Number(maxAmount)) {
    errors.push(
      buildIssue(
        file.fileName,
        row.rowNumber,
        'max_amount',
        'INVALID_AMOUNT_RANGE',
        'max_amount must be greater than or equal to min_amount.',
        'Adjust min_amount or max_amount.'
      )
    );
  }

  const chancePercent = row.values.chance_percent;
  if (chancePercent && (Number(chancePercent) < 0 || Number(chancePercent) > 100)) {
    errors.push(
      buildIssue(
        file.fileName,
        row.rowNumber,
        'chance_percent',
        'INVALID_PERCENT',
        'chance_percent must be between 0 and 100.',
        'Use a whole number between 0 and 100.'
      )
    );
  }

  const quantity = row.values.quantity;
  const unitPrice = row.values.unit_price;
  const totalPrice = row.values.total_price;
  if (quantity && unitPrice && totalPrice) {
    const expected = Number(quantity) * Number(unitPrice);
    const actual = Number(totalPrice);
    if (Number.isFinite(expected) && Number.isFinite(actual) && Math.abs(expected - actual) > 0.01) {
      warnings.push(
        buildIssue(
          file.fileName,
          row.rowNumber,
          'total_price',
          'TOTAL_MISMATCH',
          'total_price does not match quantity multiplied by unit_price.',
          'Confirm the line total or adjust quantity/unit_price.'
        )
      );
    }
  }
}

export function validateCsvRowValues(file: CsvParsedFile, row: CsvParsedRow): {
  errors: CsvPreviewError[];
  warnings: CsvPreviewWarning[];
} {
  const errors: CsvPreviewError[] = [];
  const warnings: CsvPreviewWarning[] = [];

  file.template.requiredColumns.forEach((column) => {
    if (!row.values[column]) {
      errors.push(
        buildIssue(
          file.fileName,
          row.rowNumber,
          column,
          'REQUIRED_FIELD',
          `Column "${column}" is required.`,
          'Fill in the required value before previewing again.'
        )
      );
    }
  });

  Object.entries(row.values).forEach(([column, value]) => {
    if (!value) return;

    if (EMAIL_COLUMNS.has(column) && !isValidEmail(value)) {
      errors.push(
        buildIssue(
          file.fileName,
          row.rowNumber,
          column,
          'INVALID_EMAIL',
          `"${value}" is not a valid email address.`,
          'Use a valid email address such as admin@example.com.'
        )
      );
    }

    if (BOOLEAN_COLUMNS.has(column) && !['true', 'false'].includes(value.toLowerCase())) {
      errors.push(
        buildIssue(
          file.fileName,
          row.rowNumber,
          column,
          'INVALID_BOOLEAN',
          `"${value}" is not a valid boolean.`,
          'Use true or false.'
        )
      );
    }

    if (INTEGER_COLUMNS.has(column) && !isInteger(value)) {
      errors.push(
        buildIssue(
          file.fileName,
          row.rowNumber,
          column,
          'INVALID_INTEGER',
          `"${value}" is not a valid integer.`,
          'Use a whole number.'
        )
      );
    }

    if (DECIMAL_COLUMNS.has(column) && !isDecimal(value)) {
      errors.push(
        buildIssue(
          file.fileName,
          row.rowNumber,
          column,
          'INVALID_DECIMAL',
          `"${value}" is not a valid decimal number.`,
          'Use a number such as 1000 or 1000.50.'
        )
      );
    }

    if (DATE_COLUMNS.has(column) && !isDate(value)) {
      errors.push(
        buildIssue(
          file.fileName,
          row.rowNumber,
          column,
          'INVALID_DATE',
          `"${value}" is not a valid YYYY-MM-DD date.`,
          'Use Gregorian date format YYYY-MM-DD.'
        )
      );
    }

    const allowedValues = ENUMS[column];
    if (allowedValues && !allowedValues.includes(value.toLowerCase())) {
      errors.push(
        buildIssue(
          file.fileName,
          row.rowNumber,
          column,
          'UNKNOWN_OPTION',
          `"${value}" is not an allowed value for ${column}.`,
          `Use one of: ${allowedValues.filter(Boolean).join(', ')}.`
        )
      );
    }

    if (value.length > 5000) {
      errors.push(
        buildIssue(
          file.fileName,
          row.rowNumber,
          column,
          'VALUE_TOO_LONG',
          `${column} exceeds the maximum preview length.`,
          'Shorten the value before importing.'
        )
      );
    }
  });

  validateCodeShape(file, row, errors);
  validateJson(file, row, errors);
  validateNumericalRelationships(file, row, errors, warnings);

  return { errors, warnings };
}

export function normalizeEmail(value: string): string {
  return value.trim().toLowerCase();
}

export function normalizeOrganizationCode(value: string): string {
  return value.trim().toUpperCase();
}

export function normalizeLower(value: string): string {
  return value.trim().toLowerCase();
}

export function pipeList(value: string): string[] {
  return value
    .split('|')
    .map((part) => part.trim())
    .filter(Boolean);
}

export type CsvRowValues = RowValues;
