using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using NcProgramManager.Cnc.Models;

namespace NcProgramManager.Cnc
{
    /// <summary>Validates NC program content per-manufacturer before upload.</summary>
    public sealed class NcProgramValidator : INcProgramValidator
    {
        private const int MaxBlockLength = 80;
        private const int MaxCommentLength = 32;

        private static readonly Regex FanucNamePattern = new Regex(@"^O\d{4}$", RegexOptions.Compiled);
        private static readonly Regex FanucFirstLinePattern = new Regex(@"^O\d{4}", RegexOptions.Compiled);

        private readonly CncManufacturer _manufacturer;

        public NcProgramValidator(CncManufacturer manufacturer)
        {
            _manufacturer = manufacturer;
        }

        public ValidationResult Validate(string programName, string content)
        {
            // Mazak EIA: full passthrough. Not Fanuc-restrictive — filenames preserved
            // verbatim, content sent as-is. No name/content/forbidden-char/length checks.
            if (_manufacturer == CncManufacturer.Mazak)
                return ValidationResult.Ok();

            var errors = new List<ValidationError>();

            // Name-format check (uses just the bare filename without path)
            string name = ExtractName(programName);
            ValidateName(name, errors);

            if (string.IsNullOrEmpty(content))
                return errors.Count == 0 ? ValidationResult.Ok() : ValidationResult.Fail(errors);

            // Normalise: strip CR, split on LF
            string normalized = content.Replace("\r", "");
            string[] lines = normalized.Split('\n');

            // First-line check (Fanuc/Mitsubishi).
            // FanucProgram.Standardize() prepends a leading '\n' to the program body,
            // so the O#### header is not necessarily on lines[0]. Scan the first
            // non-blank line instead of assuming index 0.
            if (_manufacturer == CncManufacturer.Fanuc || _manufacturer == CncManufacturer.Mitsubishi)
            {
                string first = null;
                for (int li = 0; li < lines.Length; li++)
                {
                    string t = lines[li].Trim();
                    if (t.Length > 0) { first = t; break; }
                }
                if (first == null || !FanucFirstLinePattern.IsMatch(first))
                    errors.Add(new ValidationError(1, "First line must be O#### program number", ValidationSeverity.Error));
            }

            bool restrictive = IsRestrictiveDialect();

            for (int i = 0; i < lines.Length; i++)
            {
                int lineNum = i + 1;
                string line = lines[i];

                if (restrictive)
                {
                    // Tab check
                    if (line.IndexOf('\t') >= 0)
                        errors.Add(new ValidationError(lineNum, "Tab character not allowed", ValidationSeverity.Error));

                    // Lowercase check
                    for (int ci = 0; ci < line.Length; ci++)
                    {
                        if (char.IsLower(line[ci]))
                        {
                            errors.Add(new ValidationError(lineNum, "Lowercase letters not allowed", ValidationSeverity.Error));
                            break;
                        }
                    }

                    // Block length
                    if (line.Length > MaxBlockLength)
                        errors.Add(new ValidationError(lineNum,
                            string.Format("Block length {0} exceeds 80 chars", line.Length),
                            ValidationSeverity.Error));

                    // Nested parens + comment length
                    ValidateParensAndComments(line, lineNum, errors);
                }

                // Forbidden chars per manufacturer
                ValidateForbiddenChars(line, lineNum, errors);
            }

            bool hasErrors = false;
            for (int i = 0; i < errors.Count; i++)
                if (errors[i].Severity == ValidationSeverity.Error) { hasErrors = true; break; }

            if (hasErrors)
                return ValidationResult.Fail(errors);
            // Preserve warnings even when overall result is Success
            return errors.Count == 0 ? ValidationResult.Ok() : ValidationResult.OkWithWarnings(errors);
        }

        // ── private helpers ───────────────────────────────────────────────────

        private bool IsRestrictiveDialect()
        {
            return _manufacturer == CncManufacturer.Fanuc
                || _manufacturer == CncManufacturer.Mitsubishi;
        }

        private void ValidateName(string name, List<ValidationError> errors)
        {
            if (string.IsNullOrEmpty(name)) return;

            switch (_manufacturer)
            {
                case CncManufacturer.Fanuc:
                case CncManufacturer.Mitsubishi:
                    if (!FanucNamePattern.IsMatch(name))
                        errors.Add(new ValidationError(0,
                            string.Format("Program name '{0}' must match O####", name),
                            ValidationSeverity.Error));
                    break;

                case CncManufacturer.Siemens:
                    if (!name.EndsWith(".MPF"))
                        errors.Add(new ValidationError(0,
                            string.Format("Program name '{0}' must end with .MPF", name),
                            ValidationSeverity.Error));
                    break;

                case CncManufacturer.Heidenhain:
                    if (!name.EndsWith(".H"))
                        errors.Add(new ValidationError(0,
                            string.Format("Program name '{0}' must end with .H", name),
                            ValidationSeverity.Error));
                    break;

                case CncManufacturer.Fagor:
                    // Estrai stem ed estensione (solo ultima estensione conta)
                    int dotIdx = name.LastIndexOf('.');
                    string stem = dotIdx >= 0 ? name.Substring(0, dotIdx) : name;
                    // Lunghezza: regola applicata sul nome completo (incluso eventuale .ext)
                    if (name.Length > 24)
                        errors.Add(new ValidationError(0,
                            string.Format("Nome programma '{0}' supera 24 caratteri", name),
                            ValidationSeverity.Error));
                    if (name.IndexOf('\u00f1') >= 0)
                        errors.Add(new ValidationError(0,
                            "Carattere '\u00f1' non ammesso nel nome programma Fagor",
                            ValidationSeverity.Error));
                    // Stem deve contenere solo lettere/cifre/spazi
                    for (int i = 0; i < stem.Length; i++)
                    {
                        char c = stem[i];
                        if (!(char.IsLetterOrDigit(c) || c == ' '))
                        {
                            errors.Add(new ValidationError(0,
                                string.Format("Carattere '{0}' non ammesso nel nome programma Fagor (solo lettere/cifre/spazi)", c),
                                ValidationSeverity.Error));
                            break;
                        }
                    }
                    break;
            }
        }

        private void ValidateParensAndComments(string line, int lineNum, List<ValidationError> errors)
        {
            int depth = 0;
            int commentStart = -1;

            for (int i = 0; i < line.Length; i++)
            {
                char c = line[i];
                if (c == '(')
                {
                    depth++;
                    if (depth > 1)
                    {
                        errors.Add(new ValidationError(lineNum, "Nested parentheses not allowed", ValidationSeverity.Error));
                        // Don't keep adding the same error for this line
                        return;
                    }
                    commentStart = i;
                }
                else if (c == ')')
                {
                    if (depth == 1 && commentStart >= 0)
                    {
                        // Extract comment content between the parens
                        int len = i - commentStart - 1;
                        if (len > MaxCommentLength)
                            errors.Add(new ValidationError(lineNum,
                                string.Format("Comment length {0} exceeds 32 chars", len),
                                ValidationSeverity.Error));
                    }
                    if (depth > 0) depth--;
                    commentStart = -1;
                }
            }
        }

        private void ValidateForbiddenChars(string line, int lineNum, List<ValidationError> errors)
        {
            switch (_manufacturer)
            {
                case CncManufacturer.Fanuc:
                case CncManufacturer.Mitsubishi:
                    for (int i = 0; i < line.Length; i++)
                    {
                        char c = line[i];
                        if (c == ';' || c == '[' || c == ']')
                        {
                            errors.Add(new ValidationError(lineNum,
                                string.Format("Forbidden char '{0}' for Fanuc/Mitsubishi", c),
                                ValidationSeverity.Error));
                        }
                        else if (c == '#')
                        {
                            errors.Add(new ValidationError(lineNum,
                                "Macro variable '#' detected — verify macro support on target CNC",
                                ValidationSeverity.Warning));
                        }
                    }
                    break;

                case CncManufacturer.Siemens:
                    for (int i = 0; i < line.Length; i++)
                    {
                        if (line[i] == ';')
                            errors.Add(new ValidationError(lineNum,
                                "Forbidden char ';' for Siemens (use () for comments)",
                                ValidationSeverity.Error));
                    }
                    break;

                case CncManufacturer.Heidenhain:
                    for (int i = 0; i < line.Length; i++)
                    {
                        char c = line[i];
                        if (c == '%' || c == ';')
                            errors.Add(new ValidationError(lineNum,
                                string.Format("Forbidden char '{0}' for Heidenhain", c),
                                ValidationSeverity.Error));
                    }
                    break;

                case CncManufacturer.Fagor:
                    // Fagor: nessuna restrizione su contenuto programma (ISO + linguaggio alto livello)
                    break;
            }
        }

        /// <summary>Extracts bare filename from a path (strips directory prefix).</summary>
        private static string ExtractName(string programName)
        {
            if (string.IsNullOrEmpty(programName)) return programName;
            int slash = programName.LastIndexOf('/');
            int backslash = programName.LastIndexOf('\\');
            int sep = slash > backslash ? slash : backslash;
            return sep >= 0 ? programName.Substring(sep + 1) : programName;
        }
    }
}
