import * as git from '../lib/git.js'
import * as branch from '../lib/branch.js'
import * as config from '../lib/config.js'
import * as provider from '../lib/provider.js'
import { alignDevelopWithRemote } from '../lib/worktree.js'
import type { PrResult, PrOptions } from './types.js'

/** Migration .cs files in a diff (excludes Designer + ModelSnapshot). */
function migrationsInDiff(files: string[]): string[] {
  return files.filter(
    (f) =>
      /\/Migrations\//i.test(f) &&
      f.endsWith('.cs') &&
      !/\.Designer\.cs$/i.test(f) &&
      !/ModelSnapshot\.cs$/i.test(f),
  )
}

export async function execute(
  opts: PrOptions,
  cwd?: string,
): Promise<PrResult> {
  try {
    // Get current branch
    const currentBranch = await git.getCurrentBranch(cwd)

    // Detect type
    const branchType = branch.detectBranchType(currentBranch)

    // Block if on main/develop
    if (branchType === 'main' || branchType === 'develop') {
      return {
        success: false,
        provider: 'unknown',
        title: '',
        source: currentBranch,
        target: '',
        draft: opts.draft,
        error: `Cannot create PR from ${branchType} branch`,
      }
    }

    // Read config
    const cfg = await config.readConfigForPlatform()

    // Resolve target + enforce the hard branch-target invariant FIRST: a feature
    // can never target main, develop must be set and ≠ main. (Throws → caught
    // below → reported as a failure, before any PR is created.)
    const targetBranch = branch.getTargetBranch(branchType, cfg)
    branch.assertBranchTarget(branchType, targetBranch, cfg)

    // Simon's rule: align local develop with origin BEFORE opening a feature PR
    // onto it (so the PR base + the squash-diff vs origin/develop are current).
    let developAligned: boolean | undefined
    const warnings: string[] = []
    if (targetBranch === cfg.git.branches.develop) {
      const dev = cfg.worktrees?.structure?.develop
      if (dev) {
        const align = await alignDevelopWithRemote(dev, cfg.git.branches.develop)
        if (!align.ok) {
          return {
            success: false,
            provider: cfg.git.provider,
            title: '',
            source: currentBranch,
            target: targetBranch,
            draft: opts.draft,
            error: `Pre-PR alignment failed: ${align.error}`,
          }
        }
        developAligned = true
      } else {
        warnings.push('No develop worktree in config — skipped local develop alignment.')
      }
    }

    // Push unpushed commits
    const aheadBehind = await git.getAheadBehind(currentBranch, `origin/${currentBranch}`, cwd)
    if (aheadBehind.ahead > 0) {
      const pushResult = await git.push(currentBranch, cwd, false, false)
      if (pushResult.exitCode !== 0) {
        return {
          success: false,
          provider: cfg.git.provider,
          title: '',
          source: currentBranch,
          target: targetBranch,
          draft: opts.draft,
          error: `Failed to push commits: ${pushResult.stderr || pushResult.stdout}`,
        }
      }
    }

    // Check for existing PR — verify its target matches before reporting success.
    const existingPR = await provider.findPR(currentBranch, cfg.git.provider, cwd)
    if (existingPR) {
      if (existingPR.target && existingPR.target !== targetBranch) {
        return {
          success: false,
          provider: cfg.git.provider,
          prNumber: existingPR.number,
          prUrl: existingPR.url,
          title: existingPR.title,
          source: currentBranch,
          target: existingPR.target,
          draft: opts.draft,
          error: `Existing PR #${existingPR.number} targets '${existingPR.target}' but a ${branchType} branch must target '${targetBranch}'. Abandon or retarget it before continuing.`,
        }
      }
      return {
        success: true,
        provider: cfg.git.provider,
        prNumber: existingPR.number,
        prUrl: existingPR.url,
        title: existingPR.title,
        source: currentBranch,
        target: existingPR.target,
        draft: opts.draft,
        developAligned,
        warnings: warnings.length ? warnings : undefined,
      }
    }

    // EF Core squash-before-PR (4.x §4.2): a feature must carry ≤1 migration.
    if (cfg.efcore?.squashBeforePR && branchType === 'feature') {
      const diffFiles = await git.getDiffFiles(`origin/${targetBranch}...HEAD`, cwd)
      const migrations = migrationsInDiff(diffFiles)
      if (migrations.length > 1) {
        return {
          success: false,
          provider: cfg.git.provider,
          title: '',
          source: currentBranch,
          target: targetBranch,
          draft: opts.draft,
          error: `Feature has ${migrations.length} migrations — squash to a single migration before PR (run /efcore squash). Found: ${migrations.map((m) => m.split(/[/\\]/).pop()).join(', ')}`,
        }
      }
    }

    // Generate title from git log
    const logs = await git.getLog(1, '%s', cwd)
    let title = logs[0] || 'PR from ' + currentBranch

    // Add prefix based on branch type
    if (branchType === 'release' && !title.startsWith('Release ')) {
      const versionMatch = currentBranch.match(/release\/(.+)/)
      title = versionMatch ? `Release ${versionMatch[1]}` : title
    } else if (branchType === 'hotfix' && !title.startsWith('Hotfix:')) {
      title = `Hotfix: ${title}`
    }

    // Generate body from recent commits
    const recentLogs = await git.getLog(5, '%h %s', cwd)
    const body = recentLogs.join('\n')

    // Create PR via provider
    const prOptions = {
      title,
      body,
      source: currentBranch,
      target: targetBranch,
      draft: opts.draft,
      provider: cfg.git.provider,
      ...(cfg.git.provider === 'azuredevops' ? { azure: provider.parseAzureDevOpsUrl(cfg.repository.remoteUrl) as any } : {}),
    }

    const createResult = await provider.createPR(prOptions, cwd)
    if (!createResult.success) {
      return {
        success: false,
        provider: cfg.git.provider,
        title,
        source: currentBranch,
        target: targetBranch,
        draft: opts.draft,
        error: createResult.error,
      }
    }

    // VERIFY the created PR actually targets the intended branch — do NOT trust
    // the request. This is the check that would have caught PR #451
    // (feature→main): read the PR back from the provider and compare.
    const created = await provider.getPR(createResult.pr!.number, cfg.git.provider, cwd)
    if (created && created.target && created.target !== targetBranch) {
      return {
        success: false,
        provider: cfg.git.provider,
        prNumber: created.number,
        prUrl: created.url,
        title,
        source: currentBranch,
        target: created.target,
        draft: opts.draft,
        error: `PR #${created.number} was created targeting '${created.target}' but a ${branchType} branch must target '${targetBranch}'. ABANDON this PR (it must NOT be merged) and retry — your gitflow config's '${cfg.git.branches.develop}' branch may be missing on the remote.`,
      }
    }

    return {
      success: true,
      provider: cfg.git.provider,
      prNumber: createResult.pr!.number,
      prUrl: createResult.pr!.url,
      title: createResult.pr!.title,
      source: currentBranch,
      target: targetBranch,
      draft: opts.draft,
      buildPassed: true,
      developAligned,
      warnings: warnings.length ? warnings : undefined,
    }
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err)
    return {
      success: false,
      provider: 'unknown',
      title: '',
      source: '',
      target: '',
      draft: opts.draft,
      error: message,
    }
  }
}
