import { Provide, Scope, ScopeEnum } from '@midwayjs/core';

export type Priority = 'low' | 'normal' | 'high';
const PRIORITY_RANK: Record<Priority, number> = { low: 0, normal: 1, high: 2 };

interface SessionState {
  sessionName: string;
  priority: Priority;
  status: 'active' | 'idle';
  activatedAt: Date;
  lastUsedAt: Date;
}

export interface SchedulerOptions {
  maxActiveSessions?: number;
  idleTimeoutMs?: number;
  queueTimeoutMs?: number;
}

export interface AcquireResult {
  granted: boolean;
  reason?: string;
  evictedSessionName?: string;
}

@Provide()
@Scope(ScopeEnum.Singleton)
export class SessionScheduler {
  private sessions = new Map<string, SessionState>();
  private waiters: Array<{
    resolve: (v: AcquireResult) => void;
    sessionName: string;
    priority: Priority;
    expireAt: number;
  }> = [];
  private maxActiveSessions: number;
  private queueTimeoutMs: number;
  private idleTimeoutMs: number;
  private idleTimers = new Map<string, NodeJS.Timeout>();
  private onIdleTimeout?: (sessionName: string) => Promise<void>;

  constructor(opts: SchedulerOptions = {}) {
    this.maxActiveSessions = opts.maxActiveSessions ?? 50;
    this.queueTimeoutMs = opts.queueTimeoutMs ?? 30_000;
    this.idleTimeoutMs = opts.idleTimeoutMs ?? 60 * 60 * 1000;
  }

  setIdleTimeoutHandler(fn: (sessionName: string) => Promise<void>): void {
    this.onIdleTimeout = fn;
  }

  recordActivate(sessionName: string, priority: Priority = 'normal'): void {
    const now = new Date();
    this.sessions.set(sessionName, {
      sessionName,
      priority,
      status: 'active',
      activatedAt: now,
      lastUsedAt: now,
    });
    this.resetIdleTimer(sessionName);
    this.tryProcessWaiters();
  }

  markIdle(sessionName: string): void {
    const s = this.sessions.get(sessionName);
    if (s) s.status = 'idle';
    this.tryProcessWaiters();
  }

  recordDeactivate(sessionName: string): void {
    this.sessions.delete(sessionName);
    this.clearIdleTimer(sessionName);
    this.tryProcessWaiters();
  }

  touch(sessionName: string): void {
    const s = this.sessions.get(sessionName);
    if (s) {
      s.status = 'active';
      s.lastUsedAt = new Date();
    }
    this.resetIdleTimer(sessionName);
  }

  async acquireSlot(
    sessionName: string,
    priority: Priority,
    queue: boolean
  ): Promise<AcquireResult> {
    const direct = this.tryAcquireImmediate(sessionName, priority);
    if (direct.granted || !queue) return direct;

    return new Promise<AcquireResult>(resolve => {
      const expireAt = Date.now() + this.queueTimeoutMs;
      const waiter = { resolve, sessionName, priority, expireAt };
      this.waiters.push(waiter);

      const timer = setTimeout(() => {
        const i = this.waiters.indexOf(waiter);
        if (i >= 0) {
          this.waiters.splice(i, 1);
          resolve({ granted: false, reason: 'QUEUE_TIMEOUT' });
        }
      }, this.queueTimeoutMs);

      const origResolve = waiter.resolve;
      waiter.resolve = (v: AcquireResult) => {
        clearTimeout(timer);
        origResolve(v);
      };
    });
  }

  private tryAcquireImmediate(
    sessionName: string,
    priority: Priority
  ): AcquireResult {
    // 已存在的 session 直接 touch 并授权
    if (this.sessions.has(sessionName)) {
      this.touch(sessionName);
      return { granted: true };
    }

    const totalCount = this.sessions.size;
    if (totalCount < this.maxActiveSessions) {
      return { granted: true };
    }

    // LRU 回收：优先驱逐最久未用的 idle session
    const idleSessions = [...this.sessions.values()]
      .filter(s => s.status === 'idle')
      .sort((a, b) => a.lastUsedAt.getTime() - b.lastUsedAt.getTime());

    if (idleSessions.length > 0) {
      const evict = idleSessions[0];
      this.sessions.delete(evict.sessionName);
      this.clearIdleTimer(evict.sessionName);
      return { granted: true, evictedSessionName: evict.sessionName };
    }

    // 优先级抢占：驱逐优先级更低的最久未用 active session
    const lowerPriority = [...this.sessions.values()]
      .filter(s => PRIORITY_RANK[s.priority] < PRIORITY_RANK[priority])
      .sort((a, b) => a.lastUsedAt.getTime() - b.lastUsedAt.getTime());

    if (lowerPriority.length > 0) {
      const evict = lowerPriority[0];
      this.sessions.delete(evict.sessionName);
      this.clearIdleTimer(evict.sessionName);
      return { granted: true, evictedSessionName: evict.sessionName };
    }

    return { granted: false, reason: 'NO_IDLE_SESSION_TO_EVICT' };
  }

  private tryProcessWaiters(): void {
    while (this.waiters.length > 0) {
      const w = this.waiters[0];
      if (Date.now() > w.expireAt) {
        this.waiters.shift();
        w.resolve({ granted: false, reason: 'QUEUE_TIMEOUT' });
        continue;
      }
      const r = this.tryAcquireImmediate(w.sessionName, w.priority);
      if (r.granted) {
        this.waiters.shift();
        w.resolve(r);
      } else {
        break;
      }
    }
  }

  private resetIdleTimer(sessionName: string): void {
    this.clearIdleTimer(sessionName);
    if (this.idleTimeoutMs <= 0 || !this.onIdleTimeout) return;
    const timer = setTimeout(() => {
      this.idleTimers.delete(sessionName);
      this.onIdleTimeout?.(sessionName).catch(() => {});
    }, this.idleTimeoutMs);
    this.idleTimers.set(sessionName, timer);
  }

  private clearIdleTimer(sessionName: string): void {
    const t = this.idleTimers.get(sessionName);
    if (t) {
      clearTimeout(t);
      this.idleTimers.delete(sessionName);
    }
  }

  getStats(): {
    activeCount: number;
    idleCount: number;
    totalCount: number;
    maxActiveSessions: number;
  } {
    const all = [...this.sessions.values()];
    return {
      activeCount: all.filter(s => s.status === 'active').length,
      idleCount: all.filter(s => s.status === 'idle').length,
      totalCount: all.length,
      maxActiveSessions: this.maxActiveSessions,
    };
  }
}
