import { Injectable } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';

export type CampaignStatus = 'DRAFT' | 'SCHEDULED' | 'RUNNING' | 'COMPLETED' | 'CANCELLED' | 'FAILED';
export type ScheduleType = 'ONCE' | 'RECURRING';

export interface Campaign {
  id: string;
  name: string;
  description: string | null;
  status: CampaignStatus;
  notificationId: string | null;
  scheduleType: ScheduleType;
  scheduledAt: Date | null;
  cronExpr: string | null;
  timezone: string;
  nextRunAt: Date | null;
  lastRunAt: Date | null;
  targetCount: number;
  sentCount: number;
  readCount: number;
  createdBy: string | null;
  createdAt: Date;
  updatedAt: Date;
}

export interface CreateCampaignDto {
  name: string;
  description?: string;
  notificationId?: string;
  scheduleType: ScheduleType;
  scheduledAt?: Date | null;
  cronExpr?: string | null;
  timezone?: string;
  createdBy?: string;
}

@Injectable()
export class CampaignRepository {
  constructor(@InjectDataSource() private readonly ds: DataSource) {}

  async create(dto: CreateCampaignDto): Promise<Campaign> {
    const result = await this.ds.query(
      `INSERT INTO public.notification_campaigns
         (name, description, notification_id, schedule_type, scheduled_at, cron_expr, timezone, next_run_at, created_by)
       VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)
       RETURNING *`,
      [
        dto.name, dto.description ?? null, dto.notificationId ?? null,
        dto.scheduleType, dto.scheduledAt ?? null, dto.cronExpr ?? null,
        dto.timezone ?? 'UTC',
        dto.scheduleType === 'ONCE' ? dto.scheduledAt ?? null : null,
        dto.createdBy ?? null,
      ],
    );
    return this.mapRow(result[0]);
  }

  async findAll(params: { status?: string; limit: number; offset: number }): Promise<{ items: Campaign[]; total: number }> {
    const conds: string[] = [];
    const args: any[] = [];
    let p = 1;
    if (params.status) { conds.push(`status = $${p++}`); args.push(params.status); }
    const where = conds.length ? `WHERE ${conds.join(' AND ')}` : '';
    const [rows, count] = await Promise.all([
      this.ds.query(
        `SELECT * FROM public.notification_campaigns ${where} ORDER BY created_at DESC LIMIT $${p} OFFSET $${p + 1}`,
        [...args, params.limit, params.offset],
      ),
      this.ds.query(`SELECT COUNT(*)::int AS total FROM public.notification_campaigns ${where}`, args),
    ]);
    return { items: rows.map(this.mapRow), total: count[0]?.total ?? 0 };
  }

  async findById(id: string): Promise<Campaign | null> {
    const rows = await this.ds.query(`SELECT * FROM public.notification_campaigns WHERE id = $1`, [id]);
    return rows.length ? this.mapRow(rows[0]) : null;
  }

  async update(id: string, dto: Partial<CreateCampaignDto> & { status?: CampaignStatus; nextRunAt?: Date | null; lastRunAt?: Date | null; sentCount?: number; readCount?: number }): Promise<Campaign | null> {
    const fields: string[] = [];
    const values: any[] = [];
    let idx = 1;
    const add = (col: string, val: any) => { fields.push(`${col} = $${idx++}`); values.push(val); };
    if (dto.name !== undefined) add('name', dto.name);
    if (dto.description !== undefined) add('description', dto.description);
    if (dto.notificationId !== undefined) add('notification_id', dto.notificationId);
    if (dto.scheduleType !== undefined) add('schedule_type', dto.scheduleType);
    if (dto.scheduledAt !== undefined) add('scheduled_at', dto.scheduledAt);
    if (dto.cronExpr !== undefined) add('cron_expr', dto.cronExpr);
    if (dto.timezone !== undefined) add('timezone', dto.timezone);
    if (dto.status !== undefined) add('status', dto.status);
    if (dto.nextRunAt !== undefined) add('next_run_at', dto.nextRunAt);
    if (dto.lastRunAt !== undefined) add('last_run_at', dto.lastRunAt);
    if (dto.sentCount !== undefined) add('sent_count', dto.sentCount);
    if (dto.readCount !== undefined) add('read_count', dto.readCount);
    fields.push('updated_at = NOW()');
    values.push(id);
    const result = await this.ds.query(
      `UPDATE public.notification_campaigns SET ${fields.join(', ')} WHERE id = $${idx} RETURNING *`,
      values,
    );
    return result.length ? this.mapRow(result[0]) : null;
  }

  async delete(id: string): Promise<void> {
    await this.ds.query(`DELETE FROM public.notification_campaigns WHERE id = $1`, [id]);
  }

  async getStats(campaignId: string): Promise<{
    targetCount: number;
    sentCount: number;
    readCount: number;
    readRate: number;
    executions: any[];
  }> {
    const [campaign, execs] = await Promise.all([
      this.findById(campaignId),
      this.ds.query(
        `SELECT * FROM public.campaign_execution_log WHERE campaign_id = $1 ORDER BY started_at DESC LIMIT 20`,
        [campaignId],
      ),
    ]);
    const target = campaign?.targetCount ?? 0;
    const sent = campaign?.sentCount ?? 0;
    const read = campaign?.readCount ?? 0;
    return {
      targetCount: target,
      sentCount: sent,
      readCount: read,
      readRate: sent > 0 ? Math.round((read / sent) * 1000) / 10 : 0,
      executions: execs,
    };
  }

  /** Find campaigns due for execution (ONCE or RECURRING with next_run_at <= now) */
  async findDue(): Promise<Campaign[]> {
    const rows = await this.ds.query(
      `SELECT * FROM public.notification_campaigns
       WHERE status = 'SCHEDULED' AND next_run_at IS NOT NULL AND next_run_at <= NOW()
       ORDER BY next_run_at ASC
       LIMIT 50`,
    );
    return rows.map(this.mapRow);
  }

  async logExecution(campaignId: string, notificationId: string | null, targetCount: number): Promise<string> {
    const result = await this.ds.query(
      `INSERT INTO public.campaign_execution_log (campaign_id, notification_id, target_count)
       VALUES ($1, $2, $3) RETURNING id`,
      [campaignId, notificationId, targetCount],
    );
    return result[0].id;
  }

  async finishExecution(execId: string, sentCount: number, status: 'COMPLETED' | 'FAILED', error?: string): Promise<void> {
    await this.ds.query(
      `UPDATE public.campaign_execution_log
       SET finished_at = NOW(), status = $2, sent_count = $3, error = $4
       WHERE id = $1`,
      [execId, status, sentCount, error ?? null],
    );
  }

  private mapRow(r: any): Campaign {
    return {
      id: r.id,
      name: r.name,
      description: r.description,
      status: r.status,
      notificationId: r.notification_id,
      scheduleType: r.schedule_type,
      scheduledAt: r.scheduled_at,
      cronExpr: r.cron_expr,
      timezone: r.timezone,
      nextRunAt: r.next_run_at,
      lastRunAt: r.last_run_at,
      targetCount: r.target_count,
      sentCount: r.sent_count,
      readCount: r.read_count,
      createdBy: r.created_by,
      createdAt: r.created_at,
      updatedAt: r.updated_at,
    };
  }
}
