using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Web.Script.Serialization;

namespace Ounibo.Catalog.Core
{
    /// <summary>
    /// .opc 数据包 (zip: catalog.json + assets)。读写入口。
    /// </summary>
    public class OpcPackage : IDisposable
    {
        /// <summary>软件支持的数据包格式版本。</summary>
        public const string SupportedSchemaVersion = "1.0";

        public Catalog Catalog { get; private set; }
        public string Path { get; private set; }

        private ZipArchive _zip;
        private Stream _stream;

        public static OpcPackage Open(string path)
        {
            var p = new OpcPackage { Path = path };
            // 整包读入内存, 立即释放文件句柄: 软件运行中仍可重建/覆盖同一 .opc (踩坑 #19)
            byte[] bytes;
            using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                var ms = new MemoryStream();
                fs.CopyTo(ms);
                bytes = ms.ToArray();
            }
            p._stream = new MemoryStream(bytes);
            p._zip = new ZipArchive(p._stream, ZipArchiveMode.Read);
            p.LoadCatalog();
            return p;
        }

        /// <summary>从流打开 (客户版嵌入资源场景)。流的所有权转移给本对象。</summary>
        public static OpcPackage OpenStream(Stream stream, string sourceName)
        {
            var p = new OpcPackage { Path = sourceName, _stream = stream };
            p._zip = new ZipArchive(stream, ZipArchiveMode.Read);
            p.LoadCatalog();
            return p;
        }

        void LoadCatalog()
        {
            var entry = _zip.GetEntry("catalog.json");
            if (entry == null)
                throw new InvalidDataException("数据包缺少 catalog.json: " + Path);
            string json;
            using (var sr = new StreamReader(entry.Open(), Encoding.UTF8))
                json = sr.ReadToEnd();
            // 大目录 (数万变体) 的 catalog.json 可能超过默认 2M 字符限制
            var ser = new JavaScriptSerializer { MaxJsonLength = int.MaxValue };
            Catalog = ser.Deserialize<Catalog>(json);
            if (Catalog == null || Catalog.series == null)
                throw new InvalidDataException("catalog.json 解析失败: " + Path);

            // 版本校验: 过新提示升级软件, 过旧/未知提示数据包损坏或过旧
            var ver = Catalog.schemaVersion ?? "";
            if (ver != SupportedSchemaVersion)
            {
                if (string.Compare(ver, SupportedSchemaVersion, StringComparison.Ordinal) > 0)
                    throw new InvalidDataException(
                        "数据包格式版本 " + ver + " 高于软件支持的 " + SupportedSchemaVersion + ", 请升级软件");
                throw new InvalidDataException("数据包格式版本不兼容或数据包损坏: " + (ver.Length == 0 ? "(无版本号)" : ver));
            }
        }

        public byte[] ReadAsset(string path)
        {
            var entry = FindEntry(path);
            if (entry == null)
                throw new FileNotFoundException("数据包内资源缺失: " + path);
            using (var ms = new MemoryStream())
            {
                using (var s = entry.Open())
                    s.CopyTo(ms);
                return ms.ToArray();
            }
        }

        public string ReadAssetText(string path)
        {
            return Encoding.UTF8.GetString(ReadAsset(path));
        }

        public bool HasAsset(string path)
        {
            return FindEntry(path) != null;
        }

        // zip 条目分隔符可能是 '\' (Windows) 或 '/', 两种都尝试
        private ZipArchiveEntry FindEntry(string path)
        {
            var e = _zip.GetEntry(path);
            if (e == null && path != null && path.Contains('/'))
                e = _zip.GetEntry(path.Replace('/', '\\'));
            if (e == null && path != null && path.Contains('\\'))
                e = _zip.GetEntry(path.Replace('\\', '/'));
            return e;
        }

        public IEnumerable<string> EntryNames
        {
            get { return _zip.Entries.Select(e => e.FullName); }
        }

        /// <summary>把变体的 STEP 写入目标完整路径 (命名模板见 Series.Naming)。</summary>
        public string ExtractStepTo(Variant v, string fullPath)
        {
            var bytes = ReadAsset(v.step);
            File.WriteAllBytes(fullPath, bytes);
            return fullPath;
        }

        /// <summary>把变体的 STEP 按默认命名 (型号.step) 写入目录，返回完整路径。</summary>
        public string ExtractStep(Variant v, string destDir)
        {
            Directory.CreateDirectory(destDir);
            var name = v.modelCode + ".step";
            return ExtractStepTo(v, System.IO.Path.Combine(destDir, name));
        }

        public void Dispose()
        {
            if (_zip != null)
            {
                _zip.Dispose();
                _zip = null;
            }
            if (_stream != null)
            {
                _stream.Dispose();
                _stream = null;
            }
        }
    }
}
