import { branchFeature, branchRelease, branchStable } from '../const.js'
import { git } from './git.js'
import { uniq as removeDuplicate, uniqBy } from './utils.js'
import semver from 'semver'

export const getProjectRootDirectory = async () => {
    const result = await git.revparse(['--show-toplevel'])

    return result
}

export const warmupGitRepo = async () => {
    await git.fetch()
}

export const getCurrentBranch = async () => {
    const result = await git.branch()

    return result.current
}

export const pushBranch = async (branchName: string) => {
    await git.push(['-u', 'origin', branchName])
}

export const getOpenReleaseBranch = async () => {
    const result = await listBranchStartingWith(branchRelease)
    return result[0]
}

export const assertOnSameBranch = async (branch: string) => {
    const currentBranch = await getCurrentBranch()
    if (branch !== currentBranch) {
        throw new Error(`You are not on the branch ${branch}`)
    }
}

export const assertNotOnSameBranch = async (branch: string) => {
    const currentBranch = await getCurrentBranch()
    if (branch === currentBranch) {
        throw new Error(`You are already on the branch ${branch}`)
    }
}

export const assertCurrentBranchIsClean = async () => {
    const statusResult = await git.status()
    const isClean = statusResult.isClean()

    if (!isClean) {
        throw new Error('Current branch is dirty, cannot continue')
    }
}

export const getLatestTag = async () => {
    const tagResult = await git.tags()
    const lastTag = tagResult.latest

    return lastTag
}

export const getLatestTags = async (tagCount: number) => {
    const tagResult = await git.tags()

    return tagResult.all.slice(-tagCount)
}

export interface ReleaseHeaderResult {
    commit: string
    author: string
    date: string
}

export const getBranchDetails = async (branch: string) => {
    const result = await git.show([branch])
    const lines = result.split('\n')

    return {
        commit: lines[0].split(' ')[1].trim(),
        author: lines[1].split(': ')[1].trim(),
        date: lines[2].split(': ')[1].trim(),
    } as ReleaseHeaderResult
}

export interface TagHeaderResult {
    tag: string
    author: string
    date: string
}

export const getTagDetails = async (tag: string) => {
    const result = await git.show([tag])
    const lines = result.split('\n')

    return {
        tag,
        author: lines[1].split(':')[1].trim(),
        date: lines[2].split(': ')[1].trim(),
    } as TagHeaderResult
}

export const createTag = async (version: string, included: string[] = []) => {
    await git.addAnnotatedTag(
        version,
        `[regit] ${version}. (${included.join(',')})`
    )
    await git.push(['origin', version])
}

export const branchExists = async (branchName: string) => {
    const isLocal = await localBranchExists(branchName)
    const isRemote = await remoteBranchExists(branchName)

    return isLocal || isRemote
}

export const localBranchExists = async (branchName: string) => {
    const result = await git.branch()
    const isLocal = result.all.find((branch) => branch === branchName)

    return Boolean(isLocal)
}

export const remoteBranchExists = async (branchName: string) => {
    const result = await git.branch()
    const isRemote = result.all.find(
        (branch) => branch === `remotes/origin/${branchName}`
    )

    return Boolean(isRemote)
}

export const initStableBranch = async () => {
    await git.checkoutLocalBranch(branchStable)
}

export const pushStableBranch = async (version: string) => {
    await git.raw(['add', './.regit'])
    await git.raw(['commit', '-m', `[regit] Init ${version}`])
    await git.push(['-u', 'origin', branchStable])
    await createTag(version)
}

export interface ListBranchResult {
    name: string
    remoteName?: string
    from: string
    show: string[]
    isPresentLocally: boolean
}

export const getBranchInfo = async (branchName: string) => {
    const allVersionsMerged = await git.raw([
        'tag',
        '--merged',
        `origin/${branchName}`,
    ])
    const allVersions = allVersionsMerged.trim().split('\n')

    const lastestVersionMerged = allVersions.sort(semver.rcompare)[0]
    const from = lastestVersionMerged

    const show = await git.show([`origin/${branchName}`])

    const showDetails = show.trim().split('\n').slice(0, 3)

    const isPresentLocally = await localBranchExists(branchName)

    return {
        name: branchName.replace('remotes/origin/', ''),
        from: from.trim(),
        show: showDetails,
        remoteName: '',
        isPresentLocally,
    }
}

export const listBranchStartingWith = async (branchName: string) => {
    const data: ListBranchResult[] = []

    const result = await git.branch()

    const allBranches = result.all
        .filter(
            (branch) =>
                branch.startsWith(branchName) ||
                branch.startsWith(`remotes/origin/${branchName}`)
        )
        .map((branch) => {
            return branch.replace('remotes/origin/', '')
        })

    const branches = removeDuplicate(allBranches)

    for (let i = 0; i < branches.length; i++) {
        const name = branches[i]
        const branchData = await getBranchInfo(name)

        data.push(branchData)
    }

    const uniq = uniqBy(data, 'name')

    return uniq
}

export const startOrCheckoutBranch = async (branchName: string) => {
    const lastTag = await getLatestTag()
    const doesBranchExists = await branchExists(branchName)

    if (doesBranchExists) {
        console.log(`Branch ${branchName} already exists`)
        await git.checkout(branchName)
    } else {
        console.log(`Creating Branch ${branchName} from tag ${lastTag}`)
        await git.checkoutBranch(branchName, `tags/${lastTag}`)
        await git.commit(`[regit] Init '${branchName}'`, {
            '--allow-empty': null,
        })
        await git.push(['-u', 'origin', branchName])
    }
}

export const mergeBranch = async (branchName: string) => {
    const result = await git.merge(['--no-ff', `origin/${branchName}`])
}

export interface ListBranchesInBranchResult extends ListBranchResult {
    upToDate: boolean
}

export const listBranchesInBranch = async (
    targetBranch: string
): Promise<ListBranchesInBranchResult[]> => {
    const allCommits = await git.raw([
        'reflog',
        'show',
        '--no-abbrev',
        targetBranch,
    ])

    const allCommitsHashes = allCommits
        .trim()
        .split('\n')
        .map((line) => {
            return line.split(' ')[0]
        })

    const firstCommit = allCommitsHashes[allCommitsHashes.length - 1]
    const lastCommit = allCommitsHashes[0]

    if (!firstCommit || !lastCommit) {
        return []
    }

    const allCommitsOnBranch = await git.log([
        targetBranch,
        `${firstCommit}..${lastCommit}`,
    ])

    const allMergedFeatureNames = allCommitsOnBranch.all
        .filter((branch) => {
            return branch.message.includes(branchFeature)
        })
        .map((branch) => {
            const match = branch.message.match(/'(.*)'/)
            if (match) {
                return match[1]
            }
        })
        .filter(Boolean)
        .map((name) => {
            return name!.replace('origin/', '')
        })

    // get merged and up to date branches
    const allCompletelyMergedFeatures = await git.branch([
        '--no-color',
        '-r',
        '--merged',
        targetBranch,
    ])

    const allCompletelyMergedFeatureNames = allCompletelyMergedFeatures.all
        .filter((name) => {
            return name.includes(branchFeature)
        })
        .map((name) => {
            return name.replace('origin/', '')
        })

    const allPartiallyMergedFeatureNames = allMergedFeatureNames.filter(
        (name) => {
            return !allCompletelyMergedFeatureNames.includes(name!)
        }
    )

    const allFeatures = await listBranchStartingWith(branchFeature)

    const mergedBranches = allFeatures.filter((feature) => {
        return allCompletelyMergedFeatureNames.includes(feature.name)
    })

    const partiallyMergedBranches = allFeatures.filter((feature) => {
        return allPartiallyMergedFeatureNames.includes(feature.name)
    })

    const result: ListBranchesInBranchResult[] = [
        ...mergedBranches.map((feature) => {
            return {
                ...feature,
                upToDate: true,
            }
        }),
        ...partiallyMergedBranches.map((feature) => {
            return {
                ...feature,
                upToDate: false,
            }
        }),
    ]

    return result
}

export const listBranchesBetweenTags = async (tag1: string, tag2: string) => {
    const result = await git.raw([
        'log',
        '--no-merges',
        '--pretty=format:"%s"',
        '--abbrev-commit',
        `${tag1}..${tag2}`,
    ])

    const branches = [...result.matchAll(/\[regit\] Init '(.*)'/g)].reduce<
        string[]
    >((acc, current) => {
        const branchName = current[1] as string
        acc.push(branchName)

        return acc
    }, [])

    const twgitBranchesCompat = [
        ...result.matchAll(/\[twgit\] Init feature '(.*)'/g),
    ].reduce<string[]>((acc, current) => {
        const branchName = current[1] as string
        acc.push(branchName)

        return acc
    }, [])

    return [...branches, ...twgitBranchesCompat]
}

export const deleteBranch = async (branchName: string) => {
    const isPresentLocally = await localBranchExists(branchName)
    if (isPresentLocally) {
        await git.deleteLocalBranch(branchName, true)
    }

    await git.push(['origin', '--delete', branchName])
}
