import {
  Injectable,
  Logger,
  NotFoundException,
  InternalServerErrorException,
} from '@nestjs/common';
import * as bcrypt from 'bcrypt';
import { PrismaService } from '../../database/prisma.service';
import {
  CreateProjectDto,
  UpdateProjectDto,
  ReorderProjectsDto,
  UpdateSiteContentDto,
  CreateClientDto,
  UpdateClientDto,
} from './dto';

@Injectable()
export class CmsService {
  private readonly logger = new Logger(CmsService.name);

  constructor(private readonly prisma: PrismaService) {}

  // ── Audit Logging ──────────────────────────────

  private async audit(
    entity: string,
    entityId: string,
    action: string,
    before?: any,
    after?: any,
  ) {
    try {
      await this.prisma.auditLog.create({
        data: {
          entity,
          entityId,
          action,
          before: before ? JSON.stringify(before) : null,
          after: after ? JSON.stringify(after) : null,
        },
      });
      this.logger.log(`[AUDIT] ${action} ${entity}:${entityId}`);
    } catch (err) {
      // Audit failure should never break the main operation
      this.logger.error(
        `[AUDIT] Failed to log ${action} ${entity}:${entityId}`,
        err.stack,
      );
    }
  }

  // ── Write Verification ─────────────────────────

  private async verifyWrite<T extends { id: string }>(
    model: string,
    id: string,
    operation: string,
  ): Promise<T> {
    const record = await (this.prisma as any)[model].findUnique({
      where: { id },
    });
    if (!record) {
      this.logger.error(
        `[VERIFY] ${operation} failed — record ${model}:${id} not found after write`,
      );
      throw new InternalServerErrorException(
        `Veritabanı yazma doğrulaması başarısız: ${model}:${id} yazıldıktan sonra bulunamadı`,
      );
    }
    this.logger.debug(`[VERIFY] ${operation} ${model}:${id} — OK`);
    return record as T;
  }

  // ── Projects ──────────────────────────────────

  async findAllProjects() {
    return this.prisma.project.findMany({
      where: { isActive: true, deletedAt: null },
      orderBy: { sortOrder: 'asc' },
    });
  }

  async findProjectById(id: string) {
    const project = await this.prisma.project.findUnique({ where: { id } });
    if (!project || project.deletedAt)
      throw new NotFoundException('Project not found');
    return project;
  }

  async createProject(dto: CreateProjectDto) {
    // Auto-set sortOrder to last position
    const lastProject = await this.prisma.project.findFirst({
      where: { deletedAt: null },
      orderBy: { sortOrder: 'desc' },
    });
    const nextOrder = (lastProject?.sortOrder ?? -1) + 1;

    const created = await this.prisma.project.create({
      data: {
        title: dto.title,
        image: dto.image,
        roles: JSON.stringify(dto.roles),
        color: dto.color ?? '#FF5733',
        sortOrder: dto.sortOrder ?? nextOrder,
      },
    });

    // Write verification
    const verified = await this.verifyWrite(
      'project',
      created.id,
      'CREATE Project',
    );

    // Audit log
    await this.audit('project', created.id, 'CREATE', null, verified);

    return verified;
  }

  async updateProject(id: string, dto: UpdateProjectDto) {
    const before = await this.findProjectById(id); // throws if not found

    await this.prisma.project.update({
      where: { id },
      data: {
        ...dto,
        roles: dto.roles ? JSON.stringify(dto.roles) : undefined,
      },
    });

    // Write verification
    const verified = await this.verifyWrite('project', id, 'UPDATE Project');

    // Audit log
    await this.audit('project', id, 'UPDATE', before, verified);

    return verified;
  }

  async deleteProject(id: string) {
    const before = await this.findProjectById(id);

    // Soft delete — projeyi silmek yerine deletedAt işaretle
    const deleted = await this.prisma.project.update({
      where: { id },
      data: { deletedAt: new Date() },
    });

    // Write verification — deletedAt'ın set edildiğini doğrula
    const verified = await (this.prisma as any).project.findUnique({
      where: { id },
    });
    if (!verified || !verified.deletedAt) {
      this.logger.error(
        `[VERIFY] Soft DELETE failed — project:${id} deletedAt is still null`,
      );
      throw new InternalServerErrorException('Silme doğrulaması başarısız');
    }

    // Audit log
    await this.audit('project', id, 'DELETE', before, null);

    return deleted;
  }

  async restoreProject(id: string) {
    const project = await this.prisma.project.findUnique({ where: { id } });
    if (!project) throw new NotFoundException('Project not found');
    if (!project.deletedAt)
      throw new NotFoundException('Project is not deleted');

    await this.prisma.project.update({
      where: { id },
      data: { deletedAt: null },
    });

    // Write verification
    const verified = await this.verifyWrite('project', id, 'RESTORE Project');

    // Audit log
    await this.audit('project', id, 'RESTORE', null, verified);

    return verified;
  }

  async reorderProjects(dto: ReorderProjectsDto) {
    const updates = dto.items.map((item) =>
      this.prisma.project.update({
        where: { id: item.id },
        data: { sortOrder: item.sortOrder },
      }),
    );
    await this.prisma.$transaction(updates);
    return this.findAllProjects();
  }

  // ── Site Content ──────────────────────────────

  async findAllContent(locale?: string) {
    const where = locale ? { locale } : {};
    const contents = await this.prisma.siteContent.findMany({ where });

    // Transform into a section-keyed object
    const result: Record<string, any> = {};
    for (const c of contents) {
      if (!result[c.locale]) result[c.locale] = {};
      try {
        result[c.locale][c.section] = JSON.parse(c.content);
      } catch {
        result[c.locale][c.section] = c.content;
      }
    }
    return result;
  }

  async findContentBySection(section: string, locale: string = 'tr') {
    const content = await this.prisma.siteContent.findUnique({
      where: { section_locale: { section, locale } },
    });
    if (!content) return null;
    try {
      return JSON.parse(content.content);
    } catch {
      return content.content;
    }
  }

  async upsertContent(
    section: string,
    locale: string,
    dto: UpdateSiteContentDto,
  ) {
    const contentStr = JSON.stringify(dto.content);

    // Get before state for audit
    const before = await this.prisma.siteContent.findUnique({
      where: { section_locale: { section, locale } },
    });

    const result = await this.prisma.siteContent.upsert({
      where: { section_locale: { section, locale } },
      create: {
        section,
        locale,
        content: contentStr,
      },
      update: {
        content: contentStr,
      },
    });

    // Audit log
    await this.audit(
      'content',
      result.id,
      before ? 'UPDATE' : 'CREATE',
      before,
      result,
    );

    return result;
  }

  // ── Media Files ──────────────────────────────

  async createMediaFile(file: {
    filename: string;
    originalName: string;
    mimetype: string;
    path: string;
    url: string;
    size: number;
  }) {
    const created = await this.prisma.mediaFile.create({ data: file });

    // Audit log
    await this.audit('media', created.id, 'CREATE', null, created);

    return created;
  }

  async findAllMedia() {
    return this.prisma.mediaFile.findMany({
      orderBy: { createdAt: 'desc' },
    });
  }

  async deleteMedia(id: string) {
    const media = await this.prisma.mediaFile.findUnique({ where: { id } });
    if (!media) throw new NotFoundException('Media not found');

    const result = await this.prisma.mediaFile.delete({ where: { id } });

    // Audit log
    await this.audit('media', id, 'DELETE', media, null);

    return result;
  }

  // ── Seed ──────────────────────────────────────

  async seedDefaultProjects() {
    const count = await this.prisma.project.count({
      where: { deletedAt: null },
    });
    if (count > 0) {
      this.logger.log('Projects already seeded, skipping');
      return;
    }

    const defaults = [
      {
        title: 'Deadpool',
        image: 'https://picsum.photos/seed/deadpool/1920/1080?blur=2',
        roles: JSON.stringify([
          'Official Turkish Voice',
          'Character Voice Acting',
        ]),
        color: '#FF5733',
        sortOrder: 0,
      },
      {
        title: 'Spider-Man',
        image: 'https://picsum.photos/seed/spiderman/1920/1080?blur=2',
        roles: JSON.stringify([
          'Official Turkish Voice',
          'Character Voice Acting',
        ]),
        color: '#C70039',
        sortOrder: 1,
      },
      {
        title: 'The Lion King',
        image: 'https://picsum.photos/seed/lionking/1920/1080?blur=2',
        roles: JSON.stringify(['Simba', 'Musical Performance', 'Vocals']),
        color: '#900C3F',
        sortOrder: 2,
      },
      {
        title: 'Monster Notebook',
        image: 'https://picsum.photos/seed/monster/1920/1080?blur=2',
        roles: JSON.stringify(['Brand Face', 'Corporate Voice']),
        color: '#511845',
        sortOrder: 3,
      },
    ];

    for (const p of defaults) {
      const created = await this.prisma.project.create({ data: p });
      await this.audit('project', created.id, 'CREATE', null, created);
    }
    this.logger.log(`Seeded ${defaults.length} default projects`);
  }

  async seedAdminUser() {
    const adminExists = await this.prisma.user.findUnique({
      where: { email: 'admin@haruncan.com' },
    });
    if (adminExists) {
      this.logger.log('Admin user already exists, skipping');
      return;
    }

    const hashedPassword = await bcrypt.hash('admin123', 10);

    await this.prisma.user.create({
      data: {
        email: 'admin@haruncan.com',
        password: hashedPassword,
        firstName: 'Harun',
        lastName: 'CAN',
        roles: {
          create: {
            role: {
              connectOrCreate: {
                where: { name: 'admin' },
                create: { name: 'admin', description: 'Administrator' },
              },
            },
          },
        },
      },
    });

    this.logger.log('✅ Admin user created: admin@haruncan.com / admin123');
  }

  // ── Clients ──────────────────────────────────

  async findAllClients() {
    return this.prisma.client.findMany({
      where: { isActive: true },
      orderBy: { sortOrder: 'asc' },
    });
  }

  async createClient(dto: CreateClientDto) {
    const lastClient = await this.prisma.client.findFirst({
      orderBy: { sortOrder: 'desc' },
    });
    const nextOrder = (lastClient?.sortOrder ?? -1) + 1;

    const created = await this.prisma.client.create({
      data: {
        name: dto.name,
        logo: dto.logo,
        website: dto.website,
        sortOrder: dto.sortOrder ?? nextOrder,
      },
    });

    // Write verification
    const verified = await this.verifyWrite(
      'client',
      created.id,
      'CREATE Client',
    );

    // Audit log
    await this.audit('client', created.id, 'CREATE', null, verified);

    return verified;
  }

  async updateClient(id: string, dto: UpdateClientDto) {
    const before = await this.prisma.client
      .findUniqueOrThrow({ where: { id } })
      .catch(() => {
        throw new NotFoundException('Client not found');
      });

    await this.prisma.client.update({
      where: { id },
      data: dto,
    });

    // Write verification
    const verified = await this.verifyWrite('client', id, 'UPDATE Client');

    // Audit log
    await this.audit('client', id, 'UPDATE', before, verified);

    return verified;
  }

  async deleteClient(id: string) {
    const client = await this.prisma.client.findUnique({ where: { id } });
    if (!client) throw new NotFoundException('Client not found');

    const result = await this.prisma.client.delete({ where: { id } });

    // Audit log
    await this.audit('client', id, 'DELETE', client, null);

    return result;
  }

  // ── Audit Logs (Admin Queries) ───────────────

  async findAuditLogs(options?: {
    entity?: string;
    entityId?: string;
    action?: string;
    limit?: number;
    offset?: number;
  }) {
    const where: any = {};
    if (options?.entity) where.entity = options.entity;
    if (options?.entityId) where.entityId = options.entityId;
    if (options?.action) where.action = options.action;

    const [logs, total] = await Promise.all([
      this.prisma.auditLog.findMany({
        where,
        orderBy: { createdAt: 'desc' },
        take: options?.limit ?? 50,
        skip: options?.offset ?? 0,
      }),
      this.prisma.auditLog.count({ where }),
    ]);

    return { logs, total };
  }

  async findAuditLogsByEntity(entity: string, entityId: string) {
    return this.prisma.auditLog.findMany({
      where: { entity, entityId },
      orderBy: { createdAt: 'desc' },
    });
  }
}
