import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { AuthenticatedUser } from '../auth/auth.types';
import { normalizeText } from '../common/normalize';
import { PrismaService } from '../prisma/prisma.service';
import { CreateTemplateDto } from './dto/create-template.dto';
import { UpdateTemplateDto } from './dto/update-template.dto';

type SeedTemplate = {
  name: string;
  category: string;
  status: string;
  language: string;
  headerText?: string;
  bodyText: string;
  footerText?: string;
  buttons?: Array<{ type: string; label: string }>;
};

const seededTemplates: SeedTemplate[] = [
  {
    name: 'order_confirmation_v2',
    category: 'Utility',
    status: 'Approved',
    language: 'en_US',
    bodyText: "Hi {{1}}, thank you for your order #{{2}}! We've received your payment and will notify you when it ships...",
    footerText: 'Reply STOP to opt out.',
    buttons: [{ type: 'quick_reply', label: 'Track Order' }],
  },
  {
    name: 'holiday_sale_promo',
    category: 'Marketing',
    status: 'Pending',
    language: 'en_US',
    headerText: 'Holiday Sale',
    bodyText: 'Exclusive Holiday Sale! Get 30% OFF on all items using code FESTIVE30. Shop now at {{1}}...',
    footerText: 'Valid through Sunday only.',
    buttons: [{ type: 'quick_reply', label: 'Shop Now' }],
  },
  {
    name: 'account_recovery_otp',
    category: 'Authentication',
    status: 'Rejected',
    language: 'en_US',
    bodyText: 'Your recovery code is {{1}}. Do not share this with anyone. This code expires in 5 minutes.',
    footerText: 'Security automation',
  },
  {
    name: 'shipping_update_express',
    category: 'Utility',
    status: 'Approved',
    language: 'en_US',
    bodyText: 'Good news! Your package is out for delivery and should arrive before 7 PM today.',
    buttons: [{ type: 'quick_reply', label: 'View Delivery' }],
  },
];

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

  async findAll(params?: { search?: string; category?: string; status?: string; language?: string }) {
    await this.ensureSeedData();

    const search = params?.search?.trim();
    const category = params?.category?.trim();
    const status = params?.status?.trim();
    const language = params?.language?.trim();

    const where: Prisma.MessageTemplateWhereInput = {
      ...(category ? { category: { equals: category, mode: 'insensitive' } } : {}),
      ...(status ? { status: { equals: status, mode: 'insensitive' } } : {}),
      ...(language ? { language: { equals: language, mode: 'insensitive' } } : {}),
      ...(search
        ? {
            OR: [
              { name: { contains: search, mode: 'insensitive' } },
              { category: { contains: search, mode: 'insensitive' } },
              { bodyText: { contains: search, mode: 'insensitive' } },
            ],
          }
        : {}),
    };

    const templates = await this.prisma.messageTemplate.findMany({
      where,
      orderBy: [{ updatedAt: 'desc' }, { createdAt: 'desc' }],
    });

    return {
      total: templates.length,
      items: templates.map((template) => this.serializeListItem(template)),
    };
  }

  async findOne(id: string) {
    await this.ensureSeedData();
    const template = await this.prisma.messageTemplate.findUnique({ where: { id } });
    if (!template) {
      throw new NotFoundException('Template not found');
    }

    return this.serializeDetail(template);
  }

  async create(dto: CreateTemplateDto, user: AuthenticatedUser, ipAddress?: string) {
    await this.ensureSeedData();
    await this.assertUniqueName(dto.name);
    const actor = await this.findActor(user.sub, user.email);

    const template = await this.prisma.messageTemplate.create({
      data: {
        name: normalizeText(dto.name)!,
        category: normalizeText(dto.category) || 'Utility',
        status: normalizeText(dto.status) || 'Draft',
        language: normalizeText(dto.language) || 'en_US',
        headerText: normalizeText(dto.headerText),
        bodyText: normalizeText(dto.bodyText) || '',
        footerText: normalizeText(dto.footerText),
        buttonsJson: this.normalizeButtons(dto.buttons),
      },
    });

    await this.prisma.auditLog.create({
      data: {
        actorUserId: actor.id,
        actorName: actor.name,
        actorEmail: actor.email,
        actionType: 'Template Created',
        module: 'Templates',
        ipAddress: ipAddress || null,
        severity: 'default',
        details: `Created template ${template.name}.`,
      },
    });

    return this.serializeDetail(template);
  }

  async update(id: string, dto: UpdateTemplateDto, user: AuthenticatedUser, ipAddress?: string) {
    await this.ensureSeedData();
    const current = await this.prisma.messageTemplate.findUnique({ where: { id } });
    if (!current) {
      throw new NotFoundException('Template not found');
    }

    if (dto.name && dto.name.trim().toLowerCase() !== current.name.toLowerCase()) {
      await this.assertUniqueName(dto.name);
    }

    const actor = await this.findActor(user.sub, user.email);
    const template = await this.prisma.messageTemplate.update({
      where: { id },
      data: {
        ...(dto.name !== undefined ? { name: normalizeText(dto.name)! } : {}),
        ...(dto.category !== undefined ? { category: normalizeText(dto.category) || current.category } : {}),
        ...(dto.status !== undefined ? { status: normalizeText(dto.status) || current.status } : {}),
        ...(dto.language !== undefined ? { language: normalizeText(dto.language) || current.language } : {}),
        ...(dto.headerText !== undefined ? { headerText: normalizeText(dto.headerText) } : {}),
        ...(dto.bodyText !== undefined ? { bodyText: normalizeText(dto.bodyText) || current.bodyText } : {}),
        ...(dto.footerText !== undefined ? { footerText: normalizeText(dto.footerText) } : {}),
        ...(dto.buttons !== undefined ? { buttonsJson: this.normalizeButtons(dto.buttons) } : {}),
      },
    });

    await this.prisma.auditLog.create({
      data: {
        actorUserId: actor.id,
        actorName: actor.name,
        actorEmail: actor.email,
        actionType: 'Template Updated',
        module: 'Templates',
        ipAddress: ipAddress || null,
        severity: 'default',
        details: `Updated template ${template.name}.`,
      },
    });

    return this.serializeDetail(template);
  }

  async assertTemplateExistsByName(name: string | null | undefined) {
    if (!name?.trim()) {
      return;
    }

    const template = await this.prisma.messageTemplate.findFirst({
      where: {
        name: { equals: name.trim(), mode: 'insensitive' },
      },
      select: { id: true },
    });

    if (!template) {
      throw new NotFoundException(`Template "${name}" not found`);
    }
  }

  private serializeListItem(template: {
    id: string;
    name: string;
    category: string;
    status: string;
    language: string;
    headerText: string | null;
    bodyText: string;
    footerText: string | null;
    buttonsJson: Prisma.JsonValue | null;
    updatedAt: Date;
  }) {
    const buttons = this.readButtons(template.buttonsJson);
    const preview = template.bodyText.length > 120 ? `${template.bodyText.slice(0, 117)}...` : template.bodyText;

    return {
      id: template.id,
      name: template.name,
      category: template.category,
      status: template.status,
      language: template.language,
      updatedAt: template.updatedAt.toISOString(),
      updatedLabel: this.relativeTimeLabel(template.updatedAt),
      preview,
      compact: buttons.length > 0,
      buttons,
    };
  }

  private serializeDetail(template: {
    id: string;
    name: string;
    category: string;
    status: string;
    language: string;
    headerText: string | null;
    bodyText: string;
    footerText: string | null;
    buttonsJson: Prisma.JsonValue | null;
    createdAt: Date;
    updatedAt: Date;
  }) {
    return {
      id: template.id,
      name: template.name,
      category: template.category,
      status: template.status,
      language: template.language,
      headerText: template.headerText,
      bodyText: template.bodyText,
      footerText: template.footerText,
      buttons: this.readButtons(template.buttonsJson),
      createdAt: template.createdAt.toISOString(),
      updatedAt: template.updatedAt.toISOString(),
    };
  }

  private normalizeButtons(buttons?: Array<{ type: string; label: string }>) {
    if (!Array.isArray(buttons)) {
      return Prisma.JsonNull;
    }

    return buttons
      .map((button) => ({
        type: normalizeText(button.type) || 'quick_reply',
        label: normalizeText(button.label) || '',
      }))
      .filter((button) => button.label.length > 0) as Prisma.InputJsonValue;
  }

  private readButtons(value: Prisma.JsonValue | null) {
    if (!Array.isArray(value)) {
      return [] as Array<{ type: string; label: string }>;
    }

    return value
      .map((item) => (item && typeof item === 'object' ? (item as Prisma.JsonObject) : null))
      .filter((item): item is Prisma.JsonObject => Boolean(item))
      .map((item) => ({
        type: typeof item.type === 'string' ? item.type : 'quick_reply',
        label: typeof item.label === 'string' ? item.label : '',
      }))
      .filter((button) => button.label.length > 0);
  }

  private async assertUniqueName(name: string) {
    const existing = await this.prisma.messageTemplate.findFirst({
      where: { name: { equals: name.trim(), mode: 'insensitive' } },
      select: { id: true },
    });

    if (existing) {
      throw new ConflictException('Template name already exists');
    }
  }

  private async ensureSeedData() {
    const count = await this.prisma.messageTemplate.count();
    if (count > 0) {
      return;
    }

    for (const template of seededTemplates) {
      await this.prisma.messageTemplate.create({
        data: {
          name: template.name,
          category: template.category,
          status: template.status,
          language: template.language,
          headerText: template.headerText || null,
          bodyText: template.bodyText,
          footerText: template.footerText || null,
          buttonsJson: template.buttons ? (template.buttons as Prisma.InputJsonValue) : Prisma.JsonNull,
        },
      });
    }
  }

  private async findActor(userId: string, email: string) {
    const actor = await this.prisma.user.findUnique({
      where: { id: userId },
      select: { id: true, name: true, email: true },
    });

    return {
      id: actor?.id || userId,
      name: actor?.name || email,
      email: actor?.email || email,
    };
  }

  private relativeTimeLabel(date: Date) {
    const diffMs = Date.now() - date.getTime();
    const diffMinutes = Math.max(1, Math.floor(diffMs / 60000));

    if (diffMinutes < 60) return `Updated ${diffMinutes}m ago`;
    const diffHours = Math.floor(diffMinutes / 60);
    if (diffHours < 24) return `Updated ${diffHours}h ago`;
    const diffDays = Math.floor(diffHours / 24);
    if (diffDays < 7) return `Updated ${diffDays}d ago`;
    return new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric', year: 'numeric' }).format(date);
  }
}
