import { Request, Response } from "express";
import * as path from "path";
import * as fs from "fs";
import * as os from "os";

const gitHttpMiddlewareFactory = require("git-http-mock-server/middleware");

// Push event tracking for tests
interface PushEvent {
  timestamp: Date;
  repo: string;
  branch: string;
  operation: "push" | "create" | "delete";
  commitSha?: string;
}

const pushEvents: PushEvent[] = [];

// Mock data for testing
const mockAccessToken = "fake_access_token_12345";
const mockDeviceCode = "fake_device_code_12345";
const mockUserCode = "FAKE-CODE";
const mockUser = {
  login: "testuser",
  id: 12345,
  email: "testuser@example.com",
};

const mockRepos = [
  {
    id: 1,
    name: "test-repo-1",
    full_name: "testuser/test-repo-1",
    private: false,
    owner: { login: "testuser" },
    default_branch: "main",
  },
  {
    id: 2,
    name: "test-repo-2",
    full_name: "testuser/test-repo-2",
    private: true,
    owner: { login: "testuser" },
    default_branch: "main",
  },
  {
    id: 3,
    name: "existing-app",
    full_name: "testuser/existing-app",
    private: false,
    owner: { login: "testuser" },
    default_branch: "main",
  },
];

const mockBranches = [
  { name: "main", commit: { sha: "abc123" } },
  { name: "develop", commit: { sha: "def456" } },
  { name: "feature/test", commit: { sha: "ghi789" } },
];

// Simple in-memory collaborator store keyed by full repo name
const repoCollaborators: Record<
  string,
  { login: string; avatar_url: string; permissions: any }[]
> = {};

// Store device flow state
let deviceFlowState = {
  deviceCode: mockDeviceCode,
  userCode: mockUserCode,
  authorized: false,
  pollCount: 0,
};

// GitHub Device Flow - Step 1: Get device code
export function handleDeviceCode(req: Request, res: Response) {
  console.log("* GitHub Device Code requested");

  // Reset state for new flow
  deviceFlowState = {
    deviceCode: mockDeviceCode,
    userCode: mockUserCode,
    authorized: false,
    pollCount: 0,
  };

  res.json({
    device_code: mockDeviceCode,
    user_code: mockUserCode,
    verification_uri: "https://github.com/login/device",
    verification_uri_complete: `https://github.com/login/device?user_code=${mockUserCode}`,
    expires_in: 900,
    interval: 1, // Short interval for testing
  });
}

// GitHub Device Flow - Step 2: Poll for access token
export function handleAccessToken(req: Request, res: Response) {
  console.log("* GitHub Access Token polling", {
    pollCount: deviceFlowState.pollCount,
  });

  const { device_code } = req.body;

  if (device_code !== mockDeviceCode) {
    return res.status(400).json({
      error: "invalid_request",
      error_description: "Invalid device code",
    });
  }

  deviceFlowState.pollCount++;

  // Simulate authorization after 3 polls (for testing)
  if (deviceFlowState.pollCount >= 3) {
    deviceFlowState.authorized = true;
    return res.json({
      access_token: mockAccessToken,
      token_type: "bearer",
      scope: "repo,user,workflow",
    });
  }

  // Return pending status
  res.status(400).json({
    error: "authorization_pending",
    error_description: "The authorization request is still pending",
  });
}

// Get authenticated user info
export function handleUser(req: Request, res: Response) {
  console.log("* GitHub User info requested");

  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.includes(mockAccessToken)) {
    return res.status(401).json({
      message: "Bad credentials",
    });
  }

  res.json(mockUser);
}

// Get user emails
export function handleUserEmails(req: Request, res: Response) {
  console.log("* GitHub User emails requested");

  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.includes(mockAccessToken)) {
    return res.status(401).json({
      message: "Bad credentials",
    });
  }

  res.json([
    {
      email: "testuser@example.com",
      primary: true,
      verified: true,
      visibility: "public",
    },
  ]);
}

// List user repositories
export function handleUserRepos(req: Request, res: Response) {
  console.log("* GitHub User repos requested");

  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.includes(mockAccessToken)) {
    return res.status(401).json({
      message: "Bad credentials",
    });
  }

  if (req.method === "GET") {
    // List repos
    res.json(mockRepos);
  } else if (req.method === "POST") {
    // Create repo
    const { name, private: isPrivate } = req.body;
    console.log("* Creating repository:", name);

    // Check if repo already exists
    const existingRepo = mockRepos.find((repo) => repo.name === name);
    if (existingRepo) {
      return res.status(422).json({
        message: "Repository creation failed.",
        errors: [
          {
            resource: "Repository",
            code: "already_exists",
            field: "name",
          },
        ],
      });
    }

    // Create new repo
    const newRepo = {
      id: mockRepos.length + 1,
      name,
      full_name: `${mockUser.login}/${name}`,
      private: !!isPrivate,
      owner: { login: mockUser.login },
      default_branch: "main",
    };

    mockRepos.push(newRepo);
    repoCollaborators[newRepo.full_name] = [
      {
        login: mockUser.login,
        avatar_url: "https://example.com/avatar.png",
        permissions: { admin: true, push: true, pull: true },
      },
    ];

    res.status(201).json(newRepo);
  }
}

// Get repository info
export function handleRepo(req: Request, res: Response) {
  console.log("* GitHub Repo info requested");

  const { owner, repo } = req.params;
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.includes(mockAccessToken)) {
    return res.status(401).json({
      message: "Bad credentials",
    });
  }

  const foundRepo = mockRepos.find((r) => r.full_name === `${owner}/${repo}`);

  if (!foundRepo) {
    return res.status(404).json({
      message: "Not Found",
    });
  }

  res.json(foundRepo);
}

// Get repository branches
export function handleRepoBranches(req: Request, res: Response) {
  console.log("* GitHub Repo branches requested");

  const { owner, repo } = req.params;
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.includes(mockAccessToken)) {
    return res.status(401).json({
      message: "Bad credentials",
    });
  }

  const foundRepo = mockRepos.find((r) => r.full_name === `${owner}/${repo}`);

  if (!foundRepo) {
    return res.status(404).json({
      message: "Not Found",
    });
  }

  res.json(mockBranches);
}

// Create repository for organization (not implemented in mock)
export function handleOrgRepos(req: Request, res: Response) {
  console.log("* GitHub Org repos requested");

  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.includes(mockAccessToken)) {
    return res.status(401).json({
      message: "Bad credentials",
    });
  }

  // For simplicity, just redirect to user repos for mock
  handleUserRepos(req, res);
}

export function handleRepoCollaborators(req: Request, res: Response) {
  console.log("* GitHub Repo collaborators requested");

  const { owner, repo } = req.params;
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.includes(mockAccessToken)) {
    return res.status(401).json({
      message: "Bad credentials",
    });
  }

  const repoName = `${owner}/${repo}`;
  const foundRepo = mockRepos.find((r) => r.full_name === repoName);
  if (!foundRepo) {
    return res.status(404).json({
      message: "Not Found",
    });
  }

  if (req.method === "GET") {
    return res.json(repoCollaborators[repoName] || []);
  }

  if (req.method === "PUT") {
    const username = req.params.username;
    const collaborators = repoCollaborators[repoName] || [];
    const existing = collaborators.find((c) => c.login === username);
    if (!existing) {
      collaborators.push({
        login: username,
        avatar_url: `https://example.com/avatars/${username}.png`,
        permissions: { pull: true, push: true, admin: false },
      });
    }
    repoCollaborators[repoName] = collaborators;
    return res.status(201).json({ invitation: true });
  }

  if (req.method === "DELETE") {
    const username = req.params.username;
    repoCollaborators[repoName] = (repoCollaborators[repoName] || []).filter(
      (c) => c.login !== username,
    );
    return res.status(204).send();
  }

  return res.status(405).json({ message: "Method not allowed" });
}

// Push event management functions for testing
export function handleGetPushEvents(req: Request, res: Response) {
  console.log("* Getting push events");
  const { repo } = req.query;

  const events = repo ? pushEvents.filter((e) => e.repo === repo) : pushEvents;

  res.json(events);
}

export function handleClearPushEvents(req: Request, res: Response) {
  console.log("* Clearing push events");
  pushEvents.length = 0;
  res.json({ cleared: true, timestamp: new Date() });
}

// Handle Git operations (push, pull, clone, etc.) using git-http-mock-server
export function handleGitPush(req: Request, res: Response, next?: Function) {
  console.log("* GitHub Git operation requested:", req.method, req.url);

  // Log request headers to see git operation details
  console.log("* Git Headers:", {
    "git-protocol": req.headers["git-protocol"],
    "content-type": req.headers["content-type"],
    "user-agent": req.headers["user-agent"],
  });

  // Create a unique temporary directory for this request
  const mockReposRoot = fs.mkdtempSync(
    path.join(
      os.tmpdir(),
      "dyad-git-mock-" + Math.random().toString(36).substring(2, 15),
    ),
  );
  console.error(`* Created temporary git repos directory: ${mockReposRoot}`);

  // Create git middleware instance for this request
  const gitHttpMiddleware = gitHttpMiddlewareFactory({
    root: mockReposRoot,
    route: "/github/git",
    glob: "*.git",
  });

  // Extract repo name from URL path like /github/git/testuser/test-repo.git
  // The middleware expects the repo name as the basename after the route
  const urlPath = req.url;
  const match = urlPath.match(/\/github\/git\/[^/]+\/([^/.]+)\.git/);
  const repoName = match?.[1];

  if (repoName) {
    console.log(`* Git operation for repo: ${repoName}`);

    // Track push events if this is a git-receive-pack (push) operation
    if (req.url.includes("/git-receive-pack") && req.method === "POST") {
      console.log("* Git PUSH operation detected for repo:", repoName);

      // Collect request body to parse git protocol
      let body = "";
      req.on("data", (chunk) => {
        body += chunk.toString();
      });
      req.on("end", () => {
        try {
          // Parse git pack protocol for branch refs
          // Git protocol sends refs in format: "old-sha new-sha refs/heads/branch-name"
          const lines = body.split("\n");
          lines.forEach((line) => {
            // Look for lines containing refs/heads/
            const refMatch = line.match(
              // eslint-disable-next-line
              /([0-9a-f]{40})\s+([0-9a-f]{40})\s+refs\/heads\/([^\s\u0000]+)/,
            );
            if (refMatch) {
              const [, oldSha, newSha, branchName] = refMatch;
              const isDelete = newSha === "0".repeat(40);
              const isCreate = oldSha === "0".repeat(40);

              let operation: "push" | "create" | "delete" = "push";
              if (isDelete) operation = "delete";
              else if (isCreate) operation = "create";

              pushEvents.push({
                timestamp: new Date(),
                repo: repoName,
                branch: branchName,
                operation,
                commitSha: isDelete ? oldSha : newSha,
              });

              console.log(
                `* Recorded ${operation} to ${repoName}/${branchName}, commit: ${isDelete ? oldSha : newSha}`,
              );
            }
          });
        } catch (error) {
          console.error("* Error parsing git protocol:", error);
        }
      });
    }

    // Ensure the bare git repository exists for this repo
    const bareRepoPath = path.join(mockReposRoot, `${repoName}.git`);
    console.log(`* Creating bare git repository at: ${bareRepoPath}`);
    try {
      fs.mkdirSync(bareRepoPath, { recursive: true });
      // Initialize as bare repository
      const { execSync } = require("child_process");
      execSync(`git init --bare`, { cwd: bareRepoPath });
      console.log(
        `* Successfully created bare git repository: ${repoName}.git`,
      );
    } catch (error) {
      console.error(`* Failed to create bare git repository:`, error);
      return res.status(500).json({
        message: "Failed to initialize git repository",
        error: error instanceof Error ? error.message : String(error),
      });
    }

    // Rewrite the URL to match what the middleware expects
    // Change /github/git/testuser/test-repo.git/... to /github/git/test-repo.git/...
    const rewrittenUrl = req.url.replace(
      /\/github\/git\/[^/]+\//,
      "/github/git/",
    );
    req.url = rewrittenUrl;
    console.log(`* Rewritten URL from ${urlPath} to ${rewrittenUrl}`);
  }

  // Use git-http-mock-server middleware to handle the actual git operations
  gitHttpMiddleware(
    req,
    res,
    next ||
      (() => {
        // Fallback if middleware doesn't handle the request
        console.log(
          `* Git middleware did not handle request: ${req.method} ${req.url}`,
        );
        res.status(404).json({
          message: "Git operation not supported",
          url: req.url,
          method: req.method,
        });
      }),
  );
}
