const std = @import("std");

/// PII detection patterns for research data privacy
pub const PIIPatterns = struct {
    email: std.regex.Regex,
    ssn: std.regex.Regex,
    phone: std.regex.Regex,
    credit_card: std.regex.Regex,
    ip_address: std.regex.Regex,

    pub fn init(allocator: std.mem.Allocator) !PIIPatterns {
        return PIIPatterns{
            .email = try std.regex.Regex.compile(allocator, "\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b"),
            .ssn = try std.regex.Regex.compile(allocator, "\\b\\d{3}-\\d{2}-\\d{4}\\b"),
            .phone = try std.regex.Regex.compile(allocator, "\\b\\d{3}-\\d{3}-\\d{4}\\b"),
            .credit_card = try std.regex.Regex.compile(allocator, "\\b(?:\\d[ -]*?){13,16}\\b"),
            .ip_address = try std.regex.Regex.compile(allocator, "\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b"),
        };
    }

    pub fn deinit(self: *PIIPatterns) void {
        self.email.deinit();
        self.ssn.deinit();
        self.phone.deinit();
        self.credit_card.deinit();
        self.ip_address.deinit();
    }
};

/// A single PII finding
pub const PIIFinding = struct {
    pii_type: []const u8,
    start_pos: usize,
    end_pos: usize,
    matched_text: []const u8,

    pub fn format(self: PIIFinding, allocator: std.mem.Allocator) ![]u8 {
        return std.fmt.allocPrint(allocator, "{s} at position {d}: '{s}'", .{
            self.pii_type, self.start_pos, self.matched_text,
        });
    }
};

/// Detect PII in text - simplified version without regex
pub fn detectPIISimple(text: []const u8, allocator: std.mem.Allocator) ![]PIIFinding {
    var findings = std.ArrayList(PIIFinding).initCapacity(allocator, 10) catch |err| {
        return err;
    };
    defer findings.deinit(allocator);

    // Check for email patterns (@ symbol with surrounding text)
    var i: usize = 0;
    while (i < text.len) : (i += 1) {
        if (text[i] == '@') {
            // Look backwards for email start
            var start = i;
            while (start > 0 and isEmailChar(text[start - 1])) {
                start -= 1;
            }

            // Look forwards for email end
            var end = i + 1;
            while (end < text.len and isEmailChar(text[end])) {
                end += 1;
            }

            // Check if it looks like an email (has . after @)
            if (i > start and end > i + 1) {
                var has_dot = false;
                for (text[i + 1 .. end]) |c| {
                    if (c == '.') {
                        has_dot = true;
                        break;
                    }
                }
                if (has_dot) {
                    try findings.append(allocator, PIIFinding{
                        .pii_type = "email",
                        .start_pos = start,
                        .end_pos = end,
                        .matched_text = text[start..end],
                    });
                }
            }
        }
    }

    // Check for IP addresses (simple pattern: XXX.XXX.XXX.XXX)
    i = 0;
    while (i < text.len) : (i += 1) {
        if (std.ascii.isDigit(text[i])) {
            var end = i;
            var dot_count: u8 = 0;
            var digit_count: u8 = 0;

            while (end < text.len and (std.ascii.isDigit(text[end]) or text[end] == '.')) {
                if (text[end] == '.') {
                    dot_count += 1;
                    digit_count = 0;
                } else {
                    digit_count += 1;
                    if (digit_count > 3) break;
                }
                if (dot_count > 3) break;
                end += 1;
            }

            // Check if it looks like an IP address (xxx.xxx.xxx.xxx pattern)
            if (dot_count == 3 and end - i >= 7 and end - i <= 15) {
                var valid = true;
                var num_start = i;
                var num_idx: u8 = 0;
                var nums: [4]u32 = undefined;

                var idx: usize = 0;
                while (idx < end - i) : (idx += 1) {
                    const c = text[i + idx];
                    if (c == '.') {
                        const num_str = text[num_start .. i + idx];
                        nums[num_idx] = std.fmt.parseInt(u32, num_str, 10) catch {
                            valid = false;
                            break;
                        };
                        if (nums[num_idx] > 255) {
                            valid = false;
                            break;
                        }
                        num_idx += 1;
                        num_start = i + idx + 1;
                    }
                }

                // Parse last number
                if (valid and num_idx == 3) {
                    const num_str = text[num_start..end];
                    if (std.fmt.parseInt(u32, num_str, 10)) |parsed_num| {
                        nums[num_idx] = parsed_num;
                        if (valid and nums[num_idx] <= 255) {
                            try findings.append(allocator, PIIFinding{
                                .pii_type = "ip_address",
                                .start_pos = i,
                                .end_pos = end,
                                .matched_text = text[i..end],
                            });
                        }
                    } else |_| {
                        valid = false;
                    }
                }
            }
        }
    }

    return findings.toOwnedSlice(allocator);
}

fn isEmailChar(c: u8) bool {
    return std.ascii.isAlphanumeric(c) or c == '.' or c == '_' or c == '%' or c == '+' or c == '-';
}

/// Scan text and return warning if PII detected
pub fn scanForPII(text: []const u8, allocator: std.mem.Allocator) !?[]const u8 {
    const findings = try detectPIISimple(text, allocator);
    defer allocator.free(findings);

    if (findings.len == 0) {
        return null;
    }

    var warning = std.ArrayList(u8).initCapacity(allocator, 256) catch |err| {
        return err;
    };
    defer warning.deinit(allocator);

    const writer = warning.writer(allocator);
    try writer.writeAll("Warning: Potential PII detected:\n");

    for (findings) |finding| {
        try writer.print("  - {s}: '{s}'\n", .{ finding.pii_type, finding.matched_text });
    }

    try writer.writeAll("Use --force to store anyway, or edit your text.");

    return try warning.toOwnedSlice(allocator);
}

/// Redact PII from text for anonymized export
pub fn redactPII(text: []const u8, allocator: std.mem.Allocator) ![]u8 {
    const findings = try detectPIISimple(text, allocator);
    defer allocator.free(findings);

    if (findings.len == 0) {
        return allocator.dupe(u8, text);
    }

    // Sort findings by position
    std.sort.sort(PIIFinding, findings, {}, compareByStartPos);

    var result = std.ArrayList(u8).initCapacity(allocator, text.len) catch |err| {
        return err;
    };
    defer result.deinit(allocator);

    var last_end: usize = 0;
    var redaction_counter: u32 = 0;

    for (findings) |finding| {
        // Append text before this finding
        if (finding.start_pos > last_end) {
            try result.appendSlice(text[last_end..finding.start_pos]);
        }

        // Append redaction placeholder
        redaction_counter += 1;
        if (std.mem.eql(u8, finding.pii_type, "email")) {
            try result.writer(allocator).print("[EMAIL-{d}]", .{redaction_counter});
        } else if (std.mem.eql(u8, finding.pii_type, "ip_address")) {
            try result.writer(allocator).print("[IP-{d}]", .{redaction_counter});
        } else {
            try result.writer(allocator).print("[REDACTED-{d}]", .{redaction_counter});
        }

        last_end = finding.end_pos;
    }

    // Append remaining text
    if (last_end < text.len) {
        try result.appendSlice(text[last_end..]);
    }

    return result.toOwnedSlice(allocator);
}

fn compareByStartPos(_: void, a: PIIFinding, b: PIIFinding) bool {
    return a.start_pos < b.start_pos;
}

/// Format findings as JSON for API responses
pub fn formatFindingsAsJson(findings: []const PIIFinding, allocator: std.mem.Allocator) ![]u8 {
    var buf = std.ArrayList(u8).initCapacity(allocator, 1024) catch |err| {
        return err;
    };
    defer buf.deinit(allocator);

    const writer = buf.writer(allocator);
    try writer.writeAll("[");

    for (findings, 0..) |finding, idx| {
        if (idx > 0) try writer.writeAll(",");
        try writer.writeAll("{");
        try writer.print("\"type\":\"{s}\",", .{finding.pii_type});
        try writer.print("\"start\":{d},", .{finding.start_pos});
        try writer.print("\"end\":{d},", .{finding.end_pos});
        try writer.writeAll("\"matched\":\"");
        // Escape the matched text
        for (finding.matched_text) |c| {
            switch (c) {
                '"' => try writer.writeAll("\\\""),
                '\\' => try writer.writeAll("\\\\"),
                '\n' => try writer.writeAll("\\n"),
                '\r' => try writer.writeAll("\\r"),
                '\t' => try writer.writeAll("\\t"),
                else => {
                    if (c < 0x20) {
                        try writer.print("\\u00{x:0>2}", .{c});
                    } else {
                        try writer.writeByte(c);
                    }
                },
            }
        }
        try writer.writeAll("\"");
        try writer.writeAll("}");
    }

    try writer.writeAll("]");
    return buf.toOwnedSlice(allocator);
}
