import { Analyzer, diffSec, Status, TestReport, WorkflowParams, secRound, convertToTestReports } from './analyzer'
import { BuildResponse, BuildLogResponse, App, BitriseStatus } from '../client/bitrise_client'
import { dropWhile, maxBy, sumBy, takeWhile } from 'lodash'
import { Artifact } from '../client/client'

type WorkflowReport = {
  // workflow
  service: 'bitrise',
  workflowId: string, // = ${app.fullname}-${triggered_workflow}
  workflowRunId: string // = ${app.fullname}-${triggered_workflow}-${build_number}
  buildNumber: number, // = build_number
  workflowName: string, // = triggered_workflow
  createdAt: Date, // = triggered_at
  trigger: string // = triggered_by
  status: Status,
  repository: string, // = app.fullname
  headSha: string, // = commit_hash
  branch: string,
  tag: string,
  jobs: JobReport[],
  startedAt: Date, // = started_on_worker_at
  completedAt: Date // = finished_at
  workflowDurationSec: number // = completedAt - startedAt
  sumJobsDurationSec: number // = sum(jobs sumStepsDurationSec)
  successCount: 0 | 1 // = 'SUCCESS': 1, others: 0
  parameters: [] // BitriseAnalyzer does not support build parameters yet
  queuedDurationSec: number // createdAt - startedAt
  commitMessage: string,
  actor: string, // manual-Kesin11
  url: string, // https://app.bitrise.io/build/{buildSlug}
}

type JobReport = {
  workflowRunId: string, // = workflowRunId
  buildNumber: number | undefined, // build_number
  jobId: string, // = build slug
  jobName: string, // workflowName
  status: Status,
  startedAt: Date,
  completedAt: Date,
  jobDurationSec: number, // = completedAt - startedAt
  sumStepsDurationSec: number // = sum(steps duration)
  steps: StepReport[],
  url: string, // https://app.bitrise.io/build/{buildSlug}
  executorClass: string, // "standard"
  executorType: string, // osx-vs4mac-stable
  executorName: '' // Bitrise does not support self-hosted runner
}

type StepReport = {
  name: string,
  status: Status,
  number: number,
  startedAt: Date,
  completedAt: Date,
  stepDurationSec: number // completedAt - startedAt
}

type JobParameter = {
  name: string
  value: string
}

type StepLog = {
  name: string
  duration: string
}

export class BitriseAnalyzer implements Analyzer {
  constructor() { }

  createWorkflowParams(app: App, build: BuildResponse): WorkflowParams {
    return {
      workflowId: `${app.fullname}-${build.triggered_workflow}`,
      workflowRunId: `${app.fullname}-${build.triggered_workflow}-${build.build_number}`,
      buildNumber: build.build_number,
      workflowName: build.triggered_workflow,
    }
  }

  createWorkflowReport(app: App, build: BuildResponse, buildLog: BuildLogResponse | null): WorkflowReport {
    const { workflowId, workflowRunId, buildNumber, workflowName }
      = this.createWorkflowParams(app, build)
    const createdAt = new Date(build.triggered_at)
    const startedAt = new Date(build.started_on_worker_at ?? build.triggered_at)
    const completedAt = new Date(build.finished_at)
    const status = this.normalizeStatus(build.status)
    const steps = this.createStepReports(startedAt, buildLog)
    const sumStepsDurationSec = secRound(sumBy(steps, 'stepDurationSec'))
    const url = `https://app.bitrise.io/build/${build.slug}`
    return {
      service: 'bitrise',
      workflowId,
      workflowRunId,
      buildNumber,
      workflowName,
      createdAt: new Date(build.triggered_at),
      trigger: build.triggered_by ?? '',
      status,
      repository: app.repo,
      headSha: build.commit_hash ?? '',
      branch: build.branch,
      tag: build.tag ?? '',
      jobs: [{
        workflowRunId,
        buildNumber,
        jobId: build.slug,
        jobName: workflowName,
        status: status,
        startedAt: startedAt,
        completedAt: completedAt,
        jobDurationSec: diffSec(startedAt, completedAt),
        sumStepsDurationSec,
        steps: steps,
        url,
        executorClass: build.machine_type_id,
        executorType: build.stack_identifier,
        executorName: '',
      }],
      startedAt,
      completedAt,
      workflowDurationSec: diffSec(startedAt, completedAt),
      sumJobsDurationSec: sumStepsDurationSec,
      successCount: (status === 'SUCCESS') ? 1 : 0,
      parameters: [],
      queuedDurationSec: diffSec(createdAt, startedAt),
      commitMessage: build.commit_message ?? '',
      actor: build.triggered_by ?? '',
      url,
    }
  }

  normalizeStatus(status: BitriseStatus): Status {
    switch (status) {
      case 1:
        return 'SUCCESS'
      case 2:
        return 'FAILURE'
      case 3:
        return 'ABORTED'
      case 4:
        return 'ABORTED'
      default:
        return 'OTHER';
    }
  }

  parseBuildLog(BuildLogResponse: BuildLogResponse): StepLog[] {
    // Extract chunk that include summary table 
    const chunks = dropWhile(BuildLogResponse.log_chunks, (chunk) => !chunk.chunk.includes('bitrise summary'))
    if (!chunks) return []

    let rows = chunks.flatMap((chunk) => chunk.chunk.split('\n'))

    // Filter summary table rows only
    rows = dropWhile(rows, (row) => !row.includes('bitrise summary'))
    rows = takeWhile(rows, (row) => !row.includes('Total runtime'))

    const steps = rows
      // Filter row that include name and step
      .filter((row) => row.match(/\d+\s(sec|min)/))
      .map((row) => {
        // Step name
        const names = [...row.matchAll(/;1m(?<name>.+?)\u001b/g)].map((match) => match.groups?.name ?? '')
        const name = maxBy(names, (name) => name.length)
        // Duration
        const duration = row.match(/\d+(\.\d+)?\s(sec|min)/)

        return {
          name: name ? name.trim() : '',
          duration: duration ? duration[0].trim() : ''
        }
      })

    return steps
  }

  createStepReports(startedAt: Date, buildLog: BuildLogResponse | null): StepReport[] {
    if (!buildLog) return []

    const steps = this.parseBuildLog(buildLog)
    let stepSumMilisec = 0
    return steps.map((step, index) => {
      const stepStartedTime = startedAt.getTime() + stepSumMilisec
      const stepMilisec = this.detectStepMilisec(step.duration)
      const stepCompletedTime = stepStartedTime + stepMilisec
      stepSumMilisec += stepMilisec

      return {
        name: this.detectStepName(step.name),
        status: this.detectStepStatus(step.name),
        number: index,
        startedAt: new Date(stepStartedTime),
        completedAt: new Date(stepCompletedTime),
        stepDurationSec: secRound(stepMilisec / 1000),
      }
    })
  }

  detectStepMilisec(durationStr: string): number {
    const [time, unit ] = durationStr.split(' ')
    switch (unit) {
      case 'sec':
        return Number(time) * 1000
      case 'min':
        return Number(time) * 60 * 1000
      default:
        return Number(time) * 1000
    }
  }

  detectStepName(stepName: string): string {
    return stepName.replace(/\s\(exit code:.+\)$/, '')
  }

  detectStepStatus(stepName: string): Status {
    if (stepName.includes('exit code: 1')) return 'FAILURE'
    return 'SUCCESS'
  }

  async createTestReports(workflowReport: WorkflowReport, junitArtifacts: Artifact[]): Promise<TestReport[]> {
    return await convertToTestReports(workflowReport, junitArtifacts)
  }
}