// swiftlint:disable file_length
import ForgejoKit
import Foundation

// swiftlint:disable:next type_body_length
struct Seeder {
    let baseURL: String
    let serviceName: String
    let composeFile: String

    private let adminUser = "testadmin"
    private let adminPass = "admin1234"
    private let testbotUser = "testbot"
    private let testbotPass = "testbot1234"
    private let readonlyUser = "readonlyuser"
    private let readonlyPass = "readonly1234"

    func seed() async throws {
        let adminClient = ForgejoClient(
            serverURL: baseURL, username: adminUser, password: adminPass,
            allowSelfSignedCertificates: true,
        )
        let testbotClient = ForgejoClient(
            serverURL: baseURL, username: testbotUser, password: testbotPass,
            allowSelfSignedCertificates: true,
        )

        let start = Date()
        let total = 7
        func step(_ number: Int, _ label: String) {
            let elapsed = Int(Date().timeIntervalSince(start))
            print("\n[\(number)/\(total)] \(label) (\(elapsed)s elapsed)")
        }

        step(1, "Creating admin user")
        try await withRetry(maxAttempts: 3, delay: .seconds(2), operation: "create admin user") {
            try createAdminUser()
        }
        step(2, "Creating API users")
        try await createAPIUsers(adminClient: adminClient)
        step(3, "Creating repositories")
        try await createRepositories(adminClient: adminClient)
        step(4, "Creating issues, files, and metadata")
        try await createIssuesFilesMetadata(adminClient: adminClient, testbotClient: testbotClient)
        step(5, "Creating pull requests")
        try await createPullRequests(testbotClient: testbotClient)
        step(6, "Setting pull request metadata")
        try await setPRMetadata(adminClient: adminClient, testbotClient: testbotClient)
        step(7, "Creating API token")
        try await createAPIToken(adminClient: adminClient)
        print("\nSeed data created successfully (\(Int(Date().timeIntervalSince(start)))s total).")
    }

    // MARK: - Phase 1: Create users

    private func createAdminUser() throws {
        print("Creating admin user...")
        try DockerExec.run(
            composeFile: composeFile, service: serviceName,
            command: [
                "forgejo", "admin", "user", "create", "--admin",
                "--username", adminUser,
                "--password", adminPass,
                "--email", "testadmin@test.local",
                "--must-change-password=false",
            ],
        )
    }

    private func createAPIUsers(adminClient: ForgejoClient) async throws {
        let adminService = AdminService(client: adminClient)

        print("Creating testbot user...")
        _ = try await adminService.createUser(
            username: testbotUser, password: testbotPass,
            email: "testbot@test.local",
        )

        print("Creating readonlyuser...")
        _ = try await adminService.createUser(
            username: readonlyUser, password: readonlyPass,
            email: "readonlyuser@test.local",
        )
    }

    // MARK: - Phase 2: Create repositories

    private func createRepositories(adminClient: ForgejoClient) async throws {
        let repoService = RepositoryService(client: adminClient)

        print("Creating repositories...")
        for (name, desc) in [
            ("test-repo", nil as String?),
            ("test-repo-2", "A second test repository" as String?),
            ("archived-repo", "An archived repository" as String?),
            ("html-readme-repo", "Repo with HTML in README" as String?),
        ] {
            _ = try await repoService.createRepository(
                name: name, description: desc, autoInit: true,
            )
        }
        print("Repositories created.")

        print("Archiving archived-repo...")
        try await withRetry(maxAttempts: 5, delay: .seconds(2), operation: "archive repo") {
            _ = try await repoService.editRepository(
                owner: adminUser, repo: "archived-repo", archived: true,
            )
        }

        // Forgejo 15 enables actions on every new repo by default. Disable it on test-repo-2 so
        // `ActionsUITests.testActionsTabAbsentForRepoWithoutActions` exercises the negative path.
        print("Disabling actions on test-repo-2...")
        try await withRetry(maxAttempts: 5, delay: .seconds(2), operation: "disable actions") {
            _ = try await repoService.editRepository(
                owner: adminUser, repo: "test-repo-2", hasActions: false,
            )
        }

        print("Updating html-readme-repo README...")
        let readmeSHA: String = try await withRetry(maxAttempts: 5, delay: .seconds(2), operation: "fetch README") {
            let file = try await repoService.fetchFileContent(
                owner: adminUser, repo: "html-readme-repo", path: "README.md",
            )
            return file.sha
        }

        _ = try await repoService.updateFile(
            owner: adminUser, repo: "html-readme-repo", path: "README.md",
            content: htmlReadmeContent, sha: readmeSHA, message: "Add HTML to README",
        )
    }

    // MARK: - Phase 3: Issues, files, metadata

    // swiftlint:disable:next function_body_length
    private func createIssuesFilesMetadata(
        adminClient: ForgejoClient, testbotClient: ForgejoClient,
    ) async throws {
        let adminIssueService = IssueService(client: adminClient)
        let testbotIssueService = IssueService(client: testbotClient)
        let repoService = RepositoryService(client: adminClient)

        print("Creating pagination test issues...")
        async let paginationTask: Void = {
            for index in 1 ... 25 {
                _ = try await adminIssueService.createIssue(
                    owner: adminUser, repo: "test-repo-2",
                    title: "Pagination test issue \(index)", body: nil,
                )
            }
        }()

        print("Creating issues as testbot...")
        for index in 1 ... 3 {
            _ = try await testbotIssueService.createIssue(
                owner: adminUser, repo: "test-repo",
                title: "Test issue \(index) from integration tests", body: nil,
            )
        }

        print("Setting up test-repo content...")
        try await withThrowingTaskGroup(of: Void.self) { group in
            group.addTask {
                try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "comment on issue #1") {
                    _ = try await testbotIssueService.createComment(
                        owner: adminUser, repo: "test-repo",
                        index: 1, body: "This is a test comment from testbot",
                    )
                }
            }

            group.addTask {
                try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "edit issue #3") {
                    _ = try await adminIssueService.editIssue(
                        owner: adminUser, repo: "test-repo", index: 3,
                        title: nil,
                        body: "This is the **markdown** body for issue 3.\n\nIt has multiple paragraphs.",
                        state: "closed",
                    )
                }
            }

            group.addTask {
                try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "edit issue #1") {
                    _ = try await adminIssueService.editIssue(
                        owner: adminUser, repo: "test-repo", index: 1,
                        title: nil,
                        body: "This is the **markdown** body for issue 1.\n\nIt has multiple paragraphs.",
                        state: nil,
                    )
                }
            }

            group.addTask {
                try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "edit issue #2") {
                    _ = try await adminIssueService.editIssue(
                        owner: adminUser, repo: "test-repo", index: 2,
                        title: nil,
                        body: "This is the **markdown** body for issue 2.\n\nIt has multiple paragraphs.",
                        state: nil,
                    )
                }
            }

            group.addTask {
                try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "add testbot collaborator") {
                    try await repoService.addCollaborator(
                        owner: adminUser, repo: "test-repo",
                        username: testbotUser, permission: "write",
                    )
                }
            }

            group.addTask {
                try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "add readonly collaborator") {
                    try await repoService.addCollaborator(
                        owner: adminUser, repo: "test-repo",
                        username: readonlyUser, permission: "read",
                    )
                }
            }

            group.addTask {
                try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "create label") {
                    _ = try await repoService.createLabel(
                        owner: adminUser, repo: "test-repo",
                        name: "bug", color: "#ee0701",
                    )
                }
            }

            group.addTask {
                try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "create milestone") {
                    _ = try await repoService.createMilestone(
                        owner: adminUser, repo: "test-repo",
                        title: "v1.0", description: "First release milestone",
                        dueOn: "2026-03-01T00:00:00Z",
                    )
                }
            }

            try await group.waitForAll()
        }

        _ = try await repoService.createFile(
            owner: adminUser, repo: "test-repo", path: "hello.py",
            content: "print(\"Hello from Forgejo!\")",
            message: "Add hello.py",
        )

        _ = try await repoService.createFile(
            owner: adminUser, repo: "test-repo", path: "src/main.py",
            content: "def main():\n    print(\"Main module\")",
            message: "Add src/main.py",
        )

        try await paginationTask

        print("Phase 3 done.")

        print("Setting issue metadata...")
        try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "add label to issue #1") {
            _ = try await adminIssueService.replaceLabels(
                owner: adminUser, repo: "test-repo",
                index: 1, labelIDs: [1],
            )
        }
        try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "set milestone on issue #1") {
            _ = try await adminIssueService.editIssue(
                owner: adminUser, repo: "test-repo", index: 1,
                title: nil, body: nil, state: nil,
                milestone: 1, assignees: [adminUser],
            )
        }
    }

    // MARK: - Phase 4: PRs

    private func createPullRequests(testbotClient: ForgejoClient) async throws {
        let repoService = RepositoryService(client: testbotClient)
        let prService = PullRequestService(client: testbotClient)

        print("Creating feature branch...")
        _ = try await repoService.createFile(
            owner: adminUser, repo: "test-repo", path: "feature.txt",
            content: "This file was added in a feature branch",
            message: "Add feature.txt",
            newBranch: "feature-branch",
        )

        print("Creating pull request #4...")
        _ = try await prService.createPullRequest(
            owner: adminUser, repo: "test-repo",
            title: "Add feature file", head: "feature-branch", base: "main",
            body: "This PR adds a new feature file.",
        )

        print("Creating merge branch...")
        _ = try await repoService.createFile(
            owner: adminUser, repo: "test-repo", path: "merge-test.txt",
            content: "This file is for merge testing",
            message: "Add merge-test.txt",
            newBranch: "merge-branch",
        )

        print("Creating pull request #5...")
        _ = try await prService.createPullRequest(
            owner: adminUser, repo: "test-repo",
            title: "Merge test PR", head: "merge-branch", base: "main",
            body: "This PR is for testing the merge flow.",
        )

        print("Creating squash-merge branch...")
        _ = try await repoService.createFile(
            owner: adminUser, repo: "test-repo", path: "squash-test.txt",
            content: "This file is for squash merge testing",
            message: "Add squash-test.txt",
            newBranch: "squash-branch",
        )

        print("Creating pull request #6...")
        _ = try await prService.createPullRequest(
            owner: adminUser, repo: "test-repo",
            title: "Squash merge test PR", head: "squash-branch", base: "main",
            body: "This PR is for testing the squash merge flow.",
        )

        print("Creating rebase branch...")
        _ = try await repoService.createFile(
            owner: adminUser, repo: "test-repo", path: "rebase-test.txt",
            content: "This file is for rebase testing",
            message: "Add rebase-test.txt",
            newBranch: "rebase-branch",
        )

        print("Creating pull request #7...")
        _ = try await prService.createPullRequest(
            owner: adminUser, repo: "test-repo",
            title: "Rebase merge test PR", head: "rebase-branch", base: "main",
            body: "This PR is for testing the rebase merge flow.",
        )
    }

    // MARK: - Phase 5: PR metadata

    private func setPRMetadata(
        adminClient: ForgejoClient, testbotClient: ForgejoClient,
    ) async throws {
        let testbotIssueService = IssueService(client: testbotClient)
        let adminPRService = PullRequestService(client: adminClient)
        let adminIssueService = IssueService(client: adminClient)

        print("Setting PR metadata...")
        try await withThrowingTaskGroup(of: Void.self) { group in
            group.addTask {
                try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "comment on PR #4") {
                    _ = try await testbotIssueService.createComment(
                        owner: adminUser, repo: "test-repo",
                        index: 4, body: "Please review this PR when you get a chance.",
                    )
                }
            }

            group.addTask {
                try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "review PR #4") {
                    _ = try await adminPRService.createReview(
                        owner: adminUser, repo: "test-repo", index: 4,
                        body: "Looks good so far, just a few comments.",
                        event: "COMMENT",
                        comments: [
                            CreateReviewComment(
                                body: "Consider a more descriptive filename.",
                                path: "feature.txt",
                                oldPosition: nil,
                                newPosition: 1,
                            ),
                        ],
                    )
                }
            }

            group.addTask {
                try await withRetry(maxAttempts: 3, delay: .seconds(1), operation: "set PR #4 metadata") {
                    _ = try await adminIssueService.editIssue(
                        owner: adminUser, repo: "test-repo", index: 4,
                        title: nil, body: nil, state: nil,
                        milestone: 1, assignees: [adminUser],
                    )
                }
            }

            try await group.waitForAll()
        }
    }

    // MARK: - Phase 6: API token

    private func createAPIToken(adminClient: ForgejoClient) async throws {
        let userService = UserService(client: adminClient)

        print("Creating API token for testadmin...")
        let token = try await userService.createToken(
            username: adminUser,
            name: "integration-test-token",
            scopes: [
                "read:user", "write:user",
                "read:repository", "write:repository",
                "read:issue", "write:issue",
                "read:notification", "write:notification",
                "read:organization",
            ],
        )

        let tokenPath = "/tmp/forgejo_test_token.txt"
        try token.sha1.write(toFile: tokenPath, atomically: true, encoding: .utf8)
        print("API token written to \(tokenPath)")
    }
}

// MARK: - HTML README content

private let htmlReadmeContent = """
# HTML Readme Test

Regular markdown paragraph.

<details>
<summary>Click to expand</summary>

This content is hidden by default.

- Item A
- Item B
</details>

## Table

<table>
<tr><th>Name</th><th>Value</th></tr>
<tr><td>Alpha</td><td>1</td></tr>
<tr><td>Beta</td><td>2</td></tr>
</table>

## Keyboard Shortcuts

Press <kbd>Ctrl</kbd>+<kbd>C</kbd> to copy.

## Image

<img src="https://via.placeholder.com/150" alt="placeholder" width="150">

## Security Test

<script>alert("xss")</script>

<img src="x" onerror="alert('xss')">

<iframe src="https://evil.example.com"></iframe>

<div onclick="alert('xss')">Click me</div>
"""
