import 'server-only';

import { mkdir, rm, writeFile } from 'node:fs/promises';
import path from 'node:path';
import { and, count, eq, inArray, isNull, sql } from 'drizzle-orm';
import {
  crmApprovalWorkflows,
  crmDocumentTemplateMappings,
  crmDocumentTemplateVersions,
  crmDocumentTemplates,
  crmReportDefinitions,
  documentSequences,
  memberships,
  msOptions,
  organizations,
  users
} from '@/db/schema';
import { getEmailConfigurationForUse } from '@/features/foundation/email/server/email-config.service';
import { SmtpEmailProvider } from '@/features/foundation/email/server/smtp-provider';
import { getStorageProvider } from '@/features/foundation/storage/service';
import { getSetupPluginRegistry } from '@/features/setup/plugins/plugin-registry';
import { db } from '@/lib/db';
import type {
  SetupCheckResult,
  SetupCheckStatus,
  SetupReadinessReport,
  SetupReportSummary
} from '../api/types';

type CheckMeta = Omit<SetupCheckResult, 'status' | 'message' | 'details' | 'checkedAt'>;

const REQUIRED_TABLES = [
  { name: 'users', table: users },
  { name: 'organizations', table: organizations },
  { name: 'memberships', table: memberships },
  { name: 'ms_options', table: msOptions },
  { name: 'document_sequences', table: documentSequences },
  { name: 'crm_approval_workflows', table: crmApprovalWorkflows },
  { name: 'crm_document_templates', table: crmDocumentTemplates },
  { name: 'crm_report_definitions', table: crmReportDefinitions }
] as const;

function now() {
  return new Date().toISOString();
}

export function buildSetupCheck(
  meta: CheckMeta,
  status: SetupCheckStatus,
  message: string,
  details?: Record<string, unknown>
): SetupCheckResult {
  return {
    ...meta,
    status,
    message,
    details,
    checkedAt: now()
  };
}

export function summarizeSetupChecks(checks: SetupCheckResult[]): SetupReportSummary {
  return {
    pass: checks.filter((check) => check.status === 'PASS').length,
    warning: checks.filter((check) => check.status === 'WARNING').length,
    fail: checks.filter((check) => check.status === 'FAIL').length,
    total: checks.length
  };
}

export function resolveSetupStatus(summary: SetupReportSummary): SetupCheckStatus {
  if (summary.fail > 0) return 'FAIL';
  if (summary.warning > 0) return 'WARNING';
  return 'PASS';
}

export function buildSetupReport(
  checks: SetupCheckResult[],
  message: string
): SetupReadinessReport {
  const summary = summarizeSetupChecks(checks);
  const status = resolveSetupStatus(summary);
  return {
    success: status !== 'FAIL',
    time: now(),
    status,
    message,
    summary,
    checks
  };
}

async function runSafeCheck(
  meta: CheckMeta,
  run: () => Promise<SetupCheckResult>
): Promise<SetupCheckResult> {
  try {
    return await run();
  } catch {
    return buildSetupCheck(
      meta,
      'FAIL',
      'The setup check failed unexpectedly.',
      { error: 'Unexpected setup check failure' }
    );
  }
}

export function checkAuthSecret(): SetupCheckResult {
  const meta: CheckMeta = {
    id: 'ENV_AUTH_SECRET',
    area: 'Environment',
    name: 'Auth secret present',
    requiredFor: ['first login'],
    purpose: 'Ensure Auth.js can sign sessions.',
    suggestedFix: 'Set AUTH_SECRET in .env.local or deployment secrets.'
  };
  const value = process.env.AUTH_SECRET?.trim() ?? '';
  if (!value) return buildSetupCheck(meta, 'FAIL', 'AUTH_SECRET is not configured.');
  if (value.length < 32) {
    return buildSetupCheck(meta, 'WARNING', 'AUTH_SECRET is present but looks short.');
  }
  return buildSetupCheck(meta, 'PASS', 'AUTH_SECRET is configured.');
}

export function checkDatabaseUrl(): SetupCheckResult {
  const meta: CheckMeta = {
    id: 'ENV_DATABASE_URL',
    area: 'Environment',
    name: 'Database URL present',
    requiredFor: ['all setup'],
    purpose: 'Ensure app can connect to PostgreSQL.',
    suggestedFix: 'Set DATABASE_URL.'
  };
  const value = process.env.DATABASE_URL?.trim() ?? '';
  if (!value) return buildSetupCheck(meta, 'FAIL', 'DATABASE_URL is not configured.');
  if (/localhost|127\.0\.0\.1/.test(value) && process.env.NODE_ENV === 'production') {
    return buildSetupCheck(meta, 'WARNING', 'DATABASE_URL points to a local host in production.');
  }
  return buildSetupCheck(meta, 'PASS', 'DATABASE_URL is configured.');
}

export async function checkDatabaseConnectivity(): Promise<SetupCheckResult> {
  const meta: CheckMeta = {
    id: 'DB_CONNECTIVITY',
    area: 'Database',
    name: 'Database connectivity',
    requiredFor: ['all setup'],
    purpose: 'Prove DB is reachable.',
    suggestedFix: 'Verify database host, network, and credentials.'
  };
  return runSafeCheck(meta, async () => {
    const startedAt = Date.now();
    await db.execute(sql`select 1`);
    const latencyMs = Date.now() - startedAt;
    if (latencyMs > 1000) {
      return buildSetupCheck(meta, 'WARNING', 'Database is reachable but responding slowly.', {
        latencyMs
      });
    }
    return buildSetupCheck(meta, 'PASS', 'Database connectivity check passed.', { latencyMs });
  });
}

export async function checkMigrationStatus(): Promise<SetupCheckResult> {
  const meta: CheckMeta = {
    id: 'DB_MIGRATIONS',
    area: 'Database',
    name: 'Migration status',
    requiredFor: ['all setup'],
    purpose: 'Prevent setup against stale schema.',
    suggestedFix: 'Run npm run db:migrate.'
  };
  return runSafeCheck(meta, async () => {
    await Promise.all(
      REQUIRED_TABLES.map(({ table }) => db.select({ value: count() }).from(table).limit(1))
    );
    return buildSetupCheck(meta, 'PASS', 'Required setup tables are queryable.', {
      checkedTables: REQUIRED_TABLES.map((table) => table.name)
    });
  });
}

export async function checkStorageProvider(): Promise<SetupCheckResult> {
  const meta: CheckMeta = {
    id: 'STORAGE_PROVIDER',
    area: 'Storage',
    name: 'Storage provider smoke test',
    requiredFor: ['document library/PDF'],
    purpose: 'Prove seed/document storage can write.',
    suggestedFix: 'Fix local directory or S3 credentials.'
  };
  return runSafeCheck(meta, async () => {
    const provider = getStorageProvider();
    const key = `setup/readiness/${crypto.randomUUID()}.txt`;
    await provider.putObject({
      key,
      body: Buffer.from('setup-readiness-smoke-test'),
      contentType: 'text/plain',
      fileName: 'readiness.txt'
    });
    const exists = await provider.objectExists({ key });
    try {
      await provider.deleteObject({ key });
    } catch {
      return buildSetupCheck(
        meta,
        'WARNING',
        'Storage write/read succeeded, but cleanup failed.',
        { key, exists }
      );
    }
    if (!exists) {
      return buildSetupCheck(meta, 'FAIL', 'Storage object was not readable after write.', { key });
    }
    return buildSetupCheck(meta, 'PASS', 'Storage write/read/delete smoke test passed.', { key });
  });
}

export async function checkUploadDirectory(): Promise<SetupCheckResult> {
  const meta: CheckMeta = {
    id: 'UPLOAD_DIRECTORY',
    area: 'Storage',
    name: 'Upload directory readiness',
    requiredFor: ['local storage'],
    purpose: 'Prove local provider path is writable.',
    suggestedFix: 'Create the upload directory or fix permissions.'
  };
  return runSafeCheck(meta, async () => {
    const provider = process.env.STORAGE_PROVIDER?.trim() || 'local';
    if (provider !== 'local') {
      return buildSetupCheck(
        meta,
        'WARNING',
        'Upload directory check is only applicable to the local storage provider.',
        { provider }
      );
    }
    const root = path.resolve(process.cwd(), process.env.LOCAL_STORAGE_ROOT?.trim() || 'storage');
    const directory = path.join(root, 'setup-readiness');
    const filePath = path.join(directory, `${crypto.randomUUID()}.txt`);
    await mkdir(directory, { recursive: true });
    await writeFile(filePath, 'setup-upload-directory-check');
    await rm(filePath, { force: true });
    return buildSetupCheck(meta, 'PASS', 'Local upload directory is writable.', { directory });
  });
}

export async function checkPdfTemplateAsset(
  organizationId?: string | null
): Promise<SetupCheckResult> {
  const meta: CheckMeta = {
    id: 'PDF_TEMPLATE_ASSET',
    area: 'PDF',
    name: 'PDF template assets exist',
    requiredFor: ['quotation'],
    purpose: 'Ensure quotation PDF can render.',
    suggestedFix: 'Run foundation/PDF template seed.'
  };
  return runSafeCheck(meta, async () => {
    if (!organizationId) {
      return buildSetupCheck(
        meta,
        'WARNING',
        'No organization context was available for PDF template verification.'
      );
    }
    const templates = await db
      .select({ id: crmDocumentTemplates.id })
      .from(crmDocumentTemplates)
      .where(
        and(
          eq(crmDocumentTemplates.organizationId, organizationId),
          eq(crmDocumentTemplates.documentType, 'quotation'),
          eq(crmDocumentTemplates.fileType, 'pdfme'),
          eq(crmDocumentTemplates.isActive, true),
          isNull(crmDocumentTemplates.deletedAt)
        )
      );
    if (!templates.length) {
      return buildSetupCheck(meta, 'FAIL', 'No active quotation PDF template exists.');
    }
    const templateIds = templates.map((template) => template.id);
    const versions = await db
      .select({ id: crmDocumentTemplateVersions.id })
      .from(crmDocumentTemplateVersions)
      .where(
        and(
          eq(crmDocumentTemplateVersions.organizationId, organizationId),
          inArray(crmDocumentTemplateVersions.templateId, templateIds),
          eq(crmDocumentTemplateVersions.isActive, true),
          isNull(crmDocumentTemplateVersions.deletedAt)
        )
      );
    if (!versions.length) {
      return buildSetupCheck(meta, 'FAIL', 'No active quotation PDF template version exists.', {
        templateCount: templates.length
      });
    }
    return buildSetupCheck(meta, 'PASS', 'Active quotation PDF template and version exist.', {
      templateCount: templates.length,
      activeVersionCount: versions.length
    });
  });
}

export async function checkPdfMappingCoverage(
  organizationId?: string | null
): Promise<SetupCheckResult> {
  const meta: CheckMeta = {
    id: 'PDF_MAPPING_COVERAGE',
    area: 'PDF',
    name: 'PDF mappings exist',
    requiredFor: ['quotation'],
    purpose: 'Ensure placeholders can resolve.',
    suggestedFix: 'Run PDF mapping seed/audit.'
  };
  return runSafeCheck(meta, async () => {
    if (!organizationId) {
      return buildSetupCheck(
        meta,
        'WARNING',
        'No organization context was available for PDF mapping verification.'
      );
    }
    const versions = await db
      .select({ id: crmDocumentTemplateVersions.id })
      .from(crmDocumentTemplateVersions)
      .innerJoin(
        crmDocumentTemplates,
        eq(crmDocumentTemplates.id, crmDocumentTemplateVersions.templateId)
      )
      .where(
        and(
          eq(crmDocumentTemplateVersions.organizationId, organizationId),
          eq(crmDocumentTemplateVersions.isActive, true),
          isNull(crmDocumentTemplateVersions.deletedAt),
          eq(crmDocumentTemplates.documentType, 'quotation'),
          eq(crmDocumentTemplates.fileType, 'pdfme'),
          isNull(crmDocumentTemplates.deletedAt)
        )
      );
    const versionIds = versions.map((row) => row.id);
    if (!versionIds.length) {
      return buildSetupCheck(meta, 'FAIL', 'No active quotation PDF template version exists.');
    }
    const [mappingCount] = await db
      .select({ value: count() })
      .from(crmDocumentTemplateMappings)
      .where(
        and(
          eq(crmDocumentTemplateMappings.organizationId, organizationId),
          inArray(crmDocumentTemplateMappings.templateVersionId, versionIds),
          isNull(crmDocumentTemplateMappings.deletedAt)
        )
      );
    const total = Number(mappingCount?.value ?? 0);
    if (total === 0) return buildSetupCheck(meta, 'FAIL', 'No PDF template mappings exist.');
    if (total < 10) {
      return buildSetupCheck(meta, 'WARNING', 'PDF mappings exist but coverage looks low.', {
        mappingCount: total
      });
    }
    return buildSetupCheck(meta, 'PASS', 'PDF mappings exist for the active quotation template.', {
      mappingCount: total
    });
  });
}

export async function checkEmailConfig(organizationId?: string | null): Promise<SetupCheckResult> {
 const meta: CheckMeta = {
 id: 'EMAIL_CONFIG',
 area: 'Email',
 name: 'Email configuration',
 requiredFor: ['optional notifications'],
 purpose: 'Confirm optional email readiness.',
 suggestedFix: 'Fix SMTP provider settings.'
 };
 return runSafeCheck(meta, async () => {
 if (!organizationId) {
 return buildSetupCheck(
 meta,
 'WARNING',
 'No organization context available for email verification yet.'
 );
 }
 const config = await getEmailConfigurationForUse(organizationId, 'smtp');
 if (!config) {
 return buildSetupCheck(
 meta,
 'WARNING',
 'Email delivery is not configured for this organization; in-app notifications can still work.'
 );
 }
 const provider = new SmtpEmailProvider();
 const result = await provider.testConnection(config);
 if (!result.ok) {
 return buildSetupCheck(
 meta,
 'FAIL',
 result.detail
 ? `Email configuration exists but SMTP connection failed: ${result.detail}`
 : 'Email configuration exists but SMTP connection failed.'
 );
 }
 return buildSetupCheck(meta, 'PASS', 'Organization email configuration is valid.');
 });
}

export async function getSetupReadinessReport(input: {
  organizationId?: string | null;
  profileId?: string | null;
} = {}): Promise<SetupReadinessReport> {
  const pluginChecks = getSetupPluginRegistry()
    .resolveReadinessChecks(input.profileId ?? 'crm_demo')
    .map((check) =>
      buildSetupCheck(
        {
          id: check.id,
          area: check.area,
          name: check.name,
          requiredFor: check.requiredFor,
          purpose: check.purpose,
          suggestedFix: check.suggestedFix
        },
        'PASS',
        `${check.name} is registered by the setup plugin registry.`
      )
    );
  const checks = await Promise.all([
    Promise.resolve(checkAuthSecret()),
    Promise.resolve(checkDatabaseUrl()),
    checkDatabaseConnectivity(),
    checkMigrationStatus(),
    checkStorageProvider(),
    checkUploadDirectory(),
    checkPdfTemplateAsset(input.organizationId),
    checkPdfMappingCoverage(input.organizationId),
    checkEmailConfig(input.organizationId)
  ]);

  return buildSetupReport([...checks, ...pluginChecks], 'Setup readiness checks completed.');
}
