import 'server-only';

import { and, desc, eq, ne } from 'drizzle-orm';
import { emailConfigurations } from '@/db/schema';
import { auditAction, auditCreate, auditDelete, auditUpdate } from '@/features/foundation/audit-log/service';
import { decryptSecret, encryptSecret } from '@/features/foundation/secrets/server/crypto';
import { db } from '@/lib/db';
import type { EmailProviderConfig } from './email-provider';

export type EmailConfigurationProviderType = 'smtp';
export type EmailConfigurationTestStatus = 'not_tested' | 'passed' | 'failed';

export type EmailConfigurationRecord = {
  id: string;
  organizationId: string;
  providerType: EmailConfigurationProviderType;
  name: string;
  host: string;
  port: number;
  secure: boolean;
  username: string | null;
  fromEmail: string;
  fromName: string | null;
  replyToEmail: string | null;
  isDefault: boolean;
  isActive: boolean;
  hasPassword: boolean;
  lastTestStatus: EmailConfigurationTestStatus;
  lastTestedAt: string | null;
  lastTestError: string | null;
  createdBy: string;
  updatedBy: string;
  createdAt: string;
  updatedAt: string;
};

export type SaveEmailConfigurationInput = {
  providerType: EmailConfigurationProviderType;
  name: string;
  host: string;
  port: number;
  secure: boolean;
  username?: string | null;
  password?: string | null;
  clearPassword?: boolean;
  fromEmail: string;
  fromName?: string | null;
  replyToEmail?: string | null;
  isDefault?: boolean;
  isActive?: boolean;
};

type EmailConfigurationRow = typeof emailConfigurations.$inferSelect;

function toIso(value: Date | null): string | null {
  return value ? value.toISOString() : null;
}

function mapRecord(row: EmailConfigurationRow): EmailConfigurationRecord {
  return {
    id: row.id,
    organizationId: row.organizationId,
    providerType: row.providerType as EmailConfigurationProviderType,
    name: row.name,
    host: row.host,
    port: row.port,
    secure: row.secure,
    username: row.username ?? null,
    fromEmail: row.fromEmail,
    fromName: row.fromName ?? null,
    replyToEmail: row.replyToEmail ?? null,
    isDefault: row.isDefault,
    isActive: row.isActive,
    hasPassword: Boolean(row.passwordEncrypted),
    lastTestStatus: row.lastTestStatus as EmailConfigurationTestStatus,
    lastTestedAt: toIso(row.lastTestedAt),
    lastTestError: row.lastTestError ?? null,
    createdBy: row.createdBy,
    updatedBy: row.updatedBy,
    createdAt: row.createdAt.toISOString(),
    updatedAt: row.updatedAt.toISOString(),
  };
}

function normalizeOptionalText(value?: string | null): string | null {
  const nextValue = value?.trim() ?? '';
  return nextValue.length > 0 ? nextValue : null;
}

function truncateErrorMessage(value: string): string {
  return value.slice(0, 500);
}

async function unsetOtherDefaults(organizationId: string, currentId?: string): Promise<void> {
  await db
    .update(emailConfigurations)
    .set({
      isDefault: false,
      updatedAt: new Date(),
    })
    .where(
      and(
        eq(emailConfigurations.organizationId, organizationId),
        currentId ? ne(emailConfigurations.id, currentId) : undefined
      )
    );
}

export async function listEmailConfigurations(organizationId: string): Promise<EmailConfigurationRecord[]> {
  const rows = await db
    .select()
    .from(emailConfigurations)
    .where(eq(emailConfigurations.organizationId, organizationId))
    .orderBy(desc(emailConfigurations.isDefault), desc(emailConfigurations.isActive), emailConfigurations.name);

  return rows.map(mapRecord);
}

export async function getEmailConfiguration(
  organizationId: string,
  id: string
): Promise<EmailConfigurationRecord | null> {
  const [row] = await db
    .select()
    .from(emailConfigurations)
    .where(and(eq(emailConfigurations.organizationId, organizationId), eq(emailConfigurations.id, id)))
    .limit(1);

  return row ? mapRecord(row) : null;
}

export async function getEmailConfigurationForUse(
  organizationId: string,
  providerType: EmailConfigurationProviderType = 'smtp'
): Promise<(EmailProviderConfig & { id: string; name: string }) | null> {
  const [row] = await db
    .select()
    .from(emailConfigurations)
    .where(
      and(
        eq(emailConfigurations.organizationId, organizationId),
        eq(emailConfigurations.providerType, providerType),
        eq(emailConfigurations.isActive, true)
      )
    )
    .orderBy(desc(emailConfigurations.isDefault), desc(emailConfigurations.updatedAt))
    .limit(1);

  if (!row) {
    return null;
  }

  return {
    id: row.id,
    name: row.name,
    providerType: row.providerType as EmailConfigurationProviderType,
    host: row.host,
    port: row.port,
    secure: row.secure,
    username: row.username ?? null,
    password: row.passwordEncrypted ? decryptSecret(row.passwordEncrypted) : null,
    fromEmail: row.fromEmail,
    fromName: row.fromName ?? null,
    replyToEmail: row.replyToEmail ?? null,
  };
}

export async function getEmailConfigurationByIdForUse(
  organizationId: string,
  id: string
): Promise<(EmailProviderConfig & { id: string; name: string }) | null> {
  const [row] = await db
    .select()
    .from(emailConfigurations)
    .where(and(eq(emailConfigurations.organizationId, organizationId), eq(emailConfigurations.id, id)))
    .limit(1);

  if (!row) {
    return null;
  }

  return {
    id: row.id,
    name: row.name,
    providerType: row.providerType as EmailConfigurationProviderType,
    host: row.host,
    port: row.port,
    secure: row.secure,
    username: row.username ?? null,
    password: row.passwordEncrypted ? decryptSecret(row.passwordEncrypted) : null,
    fromEmail: row.fromEmail,
    fromName: row.fromName ?? null,
    replyToEmail: row.replyToEmail ?? null,
  };
}

export async function createEmailConfiguration(input: {
  organizationId: string;
  userId: string;
  values: SaveEmailConfigurationInput;
}): Promise<EmailConfigurationRecord> {
  const now = new Date();
  const username = normalizeOptionalText(input.values.username);
  const shouldEncryptPassword = typeof input.values.password === 'string' && input.values.password.trim().length > 0;

  if (input.values.isDefault) {
    await unsetOtherDefaults(input.organizationId);
  }

  const [created] = await db
    .insert(emailConfigurations)
    .values({
      id: crypto.randomUUID(),
      organizationId: input.organizationId,
      providerType: input.values.providerType,
      name: input.values.name.trim(),
      host: input.values.host.trim(),
      port: input.values.port,
      secure: input.values.secure,
      username,
      passwordEncrypted: shouldEncryptPassword ? encryptSecret(input.values.password!.trim()) : null,
      fromEmail: input.values.fromEmail.trim().toLowerCase(),
      fromName: normalizeOptionalText(input.values.fromName),
      replyToEmail: normalizeOptionalText(input.values.replyToEmail)?.toLowerCase() ?? null,
      isDefault: Boolean(input.values.isDefault),
      isActive: input.values.isActive ?? true,
      lastTestStatus: 'not_tested',
      createdBy: input.userId,
      updatedBy: input.userId,
      createdAt: now,
      updatedAt: now,
    })
    .returning();

  await auditCreate({
    organizationId: input.organizationId,
    userId: input.userId,
    entityType: 'email_configuration',
    entityId: created.id,
    afterData: {
      providerType: created.providerType,
      name: created.name,
      host: created.host,
      port: created.port,
      secure: created.secure,
      username: created.username,
      fromEmail: created.fromEmail,
      isDefault: created.isDefault,
      isActive: created.isActive,
      hasPassword: Boolean(created.passwordEncrypted),
    },
  });

  return mapRecord(created);
}

export async function updateEmailConfiguration(input: {
  organizationId: string;
  userId: string;
  id: string;
  values: SaveEmailConfigurationInput;
}): Promise<EmailConfigurationRecord> {
  const [existing] = await db
    .select()
    .from(emailConfigurations)
    .where(and(eq(emailConfigurations.organizationId, input.organizationId), eq(emailConfigurations.id, input.id)))
    .limit(1);

  if (!existing) {
    throw new Error('Email configuration was not found.');
  }

  if (input.values.isDefault) {
    await unsetOtherDefaults(input.organizationId, input.id);
  }

  const passwordEncrypted =
    input.values.clearPassword === true
      ? null
      : typeof input.values.password === 'string' && input.values.password.trim().length > 0
        ? encryptSecret(input.values.password.trim())
        : existing.passwordEncrypted;

  const [updated] = await db
    .update(emailConfigurations)
    .set({
      providerType: input.values.providerType,
      name: input.values.name.trim(),
      host: input.values.host.trim(),
      port: input.values.port,
      secure: input.values.secure,
      username: normalizeOptionalText(input.values.username),
      passwordEncrypted,
      fromEmail: input.values.fromEmail.trim().toLowerCase(),
      fromName: normalizeOptionalText(input.values.fromName),
      replyToEmail: normalizeOptionalText(input.values.replyToEmail)?.toLowerCase() ?? null,
      isDefault: Boolean(input.values.isDefault),
      isActive: input.values.isActive ?? existing.isActive,
      updatedBy: input.userId,
      updatedAt: new Date(),
    })
    .where(and(eq(emailConfigurations.organizationId, input.organizationId), eq(emailConfigurations.id, input.id)))
    .returning();

  await auditUpdate({
    organizationId: input.organizationId,
    userId: input.userId,
    entityType: 'email_configuration',
    entityId: updated.id,
    beforeData: {
      ...mapRecord(existing),
      hasPassword: Boolean(existing.passwordEncrypted),
    },
    afterData: mapRecord(updated),
  });

  return mapRecord(updated);
}

export async function deleteEmailConfiguration(input: {
  organizationId: string;
  userId: string;
  id: string;
}): Promise<void> {
  const [existing] = await db
    .select()
    .from(emailConfigurations)
    .where(and(eq(emailConfigurations.organizationId, input.organizationId), eq(emailConfigurations.id, input.id)))
    .limit(1);

  if (!existing) {
    throw new Error('Email configuration was not found.');
  }

  await db
    .delete(emailConfigurations)
    .where(and(eq(emailConfigurations.organizationId, input.organizationId), eq(emailConfigurations.id, input.id)));

  if (existing.isDefault) {
    const [fallback] = await db
      .select()
      .from(emailConfigurations)
      .where(eq(emailConfigurations.organizationId, input.organizationId))
      .orderBy(desc(emailConfigurations.isActive), desc(emailConfigurations.updatedAt))
      .limit(1);

    if (fallback) {
      await db
        .update(emailConfigurations)
        .set({
          isDefault: true,
          updatedBy: input.userId,
          updatedAt: new Date(),
        })
        .where(eq(emailConfigurations.id, fallback.id));
    }
  }

  await auditDelete({
    organizationId: input.organizationId,
    userId: input.userId,
    entityType: 'email_configuration',
    entityId: input.id,
    beforeData: mapRecord(existing),
  });
}

export async function markEmailConfigurationTestResult(input: {
  organizationId: string;
  userId: string;
  id: string;
  status: Exclude<EmailConfigurationTestStatus, 'not_tested'>;
  errorMessage?: string | null;
}): Promise<EmailConfigurationRecord> {
  const [updated] = await db
    .update(emailConfigurations)
    .set({
      lastTestStatus: input.status,
      lastTestedAt: new Date(),
      lastTestError: input.errorMessage ? truncateErrorMessage(input.errorMessage) : null,
      updatedBy: input.userId,
      updatedAt: new Date(),
    })
    .where(and(eq(emailConfigurations.organizationId, input.organizationId), eq(emailConfigurations.id, input.id)))
    .returning();

  if (!updated) {
    throw new Error('Email configuration was not found.');
  }

  await auditAction({
    organizationId: input.organizationId,
    userId: input.userId,
    entityType: 'email_configuration',
    entityId: input.id,
    action: input.status === 'passed' ? 'email_configuration_test_passed' : 'email_configuration_test_failed',
    afterData: {
      lastTestStatus: updated.lastTestStatus,
      lastTestedAt: toIso(updated.lastTestedAt),
      lastTestError: updated.lastTestError,
    },
  });

  return mapRecord(updated);
}

export async function hasActiveEmailConfiguration(organizationId: string): Promise<boolean> {
  const [row] = await db
    .select({ id: emailConfigurations.id })
    .from(emailConfigurations)
    .where(
      and(
        eq(emailConfigurations.organizationId, organizationId),
        eq(emailConfigurations.isActive, true)
      )
    )
    .limit(1);

  return Boolean(row);
}
