import 'package:aves/model/entry/extensions/metadata_edition.dart';
import 'package:aves/ref/metadata/xmp.dart';
import 'package:aves/utils/xmp_utils.dart';
import 'package:test/test.dart';
import 'package:xml/xml.dart';

void main() {
  const toolkit = 'test-toolkit';

  String? _toExpect(String? xmpString) => xmpString != null
      ? XmlDocument.parse(xmpString).toXmlString(
          pretty: true,
          sortAttributes: (a, b) => a.name.qualified.compareTo(b.name.qualified),
        )
      : null;

  List<XmlNode> _getDescriptions(String xmpString) {
    final xmpDoc = XmlDocument.parse(xmpString);
    final root = xmpDoc.rootElement;
    final rdf = root.getElement(XmpElements.rdfRoot, namespace: XmpNamespaces.rdf);
    return rdf!.children.where((node) {
      return node is XmlElement && node.name.local == XmpElements.rdfDescription && node.name.namespaceUri == XmpNamespaces.rdf;
    }).toList();
  }

  const inMultiDescriptionRatings = '''
<x:xmpmeta xmlns:x="adobe:ns:meta/">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description xmlns:xmp="http://ns.adobe.com/xap/1.0/" rdf:about="uuid:faf5bdd5-ba3d-11da-ad31-d33d75182f1b">
      <xmp:Rating>5</xmp:Rating>
    </rdf:Description>
    <rdf:Description xmlns:MicrosoftPhoto="http://ns.microsoft.com/photo/1.0/" rdf:about="uuid:faf5bdd5-ba3d-11da-ad31-d33d75182f1b">
      <MicrosoftPhoto:Rating>99</MicrosoftPhoto:Rating>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>
''';
  const inRatingAttribute = '''
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core Test.SNAPSHOT">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description xmlns:xmp="http://ns.adobe.com/xap/1.0/" rdf:about="" xmp:Rating="5" />
  </rdf:RDF>
</x:xmpmeta>
''';
  const inRatingElement = '''
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core Test.SNAPSHOT">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description xmlns:xmp="http://ns.adobe.com/xap/1.0/" rdf:about="">
      <xmp:Rating>5</xmp:Rating>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>
''';
  const inSubjects = '''
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core Test.SNAPSHOT">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about=""
      xmlns:dc="http://purl.org/dc/elements/1.1/">
      <dc:subject>
        <rdf:Bag>
          <rdf:li>the king</rdf:li>
        </rdf:Bag>
      </dc:subject>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>
''';
  const inSubjectsCreator = '''
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core Test.SNAPSHOT">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about=""
        xmlns:xmp="http://ns.adobe.com/xap/1.0/"
        xmlns:dc="http://purl.org/dc/elements/1.1/"
      xmp:ModifyDate="2021-12-24T21:41:46+09:00"
      xmp:MetadataDate="2021-12-24T21:41:46+09:00">
      <dc:creator>
        <rdf:Seq>
          <rdf:li>c</rdf:li>
        </rdf:Seq>
      </dc:creator>
      <dc:subject>
        <rdf:Bag>
          <rdf:li>a</rdf:li>
          <rdf:li>b</rdf:li>
        </rdf:Bag>
      </dc:subject>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>
''';
  const inMotionPhotoMicroVideo = '''
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.1.0-jc003">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about=""
      xmlns:GCamera="http://ns.google.com/photos/1.0/camera/"
      GCamera:MicroVideo="1"
      GCamera:MicroVideoVersion="1"
      GCamera:MicroVideoOffset="1228513"
      GCamera:MicroVideoPresentationTimestampUs="233246"/>
  </rdf:RDF>
</x:xmpmeta>
''';
  const inMotionPhotoContainer = '''
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.1.0-jc003">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about=""
      xmlns:GCamera="http://ns.google.com/photos/1.0/camera/"
      xmlns:Container="http://ns.google.com/photos/1.0/container/"
      xmlns:Item="http://ns.google.com/photos/1.0/container/item/"
      GCamera:MotionPhoto="1"
      GCamera:MotionPhotoVersion="1"
      GCamera:MotionPhotoPresentationTimestampUs="2306056">
      <Container:Directory>
        <rdf:Seq>
          <rdf:li rdf:parseType="Resource">
            <Container:Item
              Item:Mime="image/jpeg"
              Item:Semantic="Primary"
              Item:Length="0"
              Item:Padding="59"/>
          </rdf:li>
          <rdf:li rdf:parseType="Resource">
            <Container:Item
              Item:Mime="video/mp4"
              Item:Semantic="MotionPhoto"
              Item:Length="3491777"
              Item:Padding="0"/>
          </rdf:li>
        </rdf:Seq>
      </Container:Directory>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>
''';

  test('Get string', () async {
    expect(XMP.getString(_getDescriptions(inRatingAttribute), XmpElements.xmpRating, namespace: XmpNamespaces.xmp), '5');
    expect(XMP.getString(_getDescriptions(inRatingElement), XmpElements.xmpRating, namespace: XmpNamespaces.xmp), '5');
    expect(XMP.getString(_getDescriptions(inSubjects), XmpElements.xmpRating, namespace: XmpNamespaces.xmp), null);
  });

  test('Set tags without existing XMP', () async {
    final modifyDate = DateTime.now();
    final xmpDate = XMP.toXmpDate(modifyDate);

    expect(
        _toExpect(await XMP.edit(
          null,
          toolkit,
          (descriptions) => ExtraAvesEntryMetadataEdition.editTagsXmp(descriptions, {'one', 'two'}),
          modifyDate: modifyDate,
        )),
        _toExpect('''
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="$toolkit">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about=""
      xmlns:dc="http://purl.org/dc/elements/1.1/"
      xmlns:xmp="http://ns.adobe.com/xap/1.0/"
      xmp:MetadataDate="$xmpDate"
      xmp:ModifyDate="$xmpDate">
      <dc:subject>
        <rdf:Bag>
          <rdf:li>one</rdf:li>
          <rdf:li>two</rdf:li>
        </rdf:Bag>
      </dc:subject>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>
'''));
  });

  test('Set tags to XMP with ratings (multiple descriptions)', () async {
    final modifyDate = DateTime.now();
    final xmpDate = XMP.toXmpDate(modifyDate);

    expect(
        _toExpect(await XMP.edit(
          inMultiDescriptionRatings,
          toolkit,
          (descriptions) => ExtraAvesEntryMetadataEdition.editTagsXmp(descriptions, {'one', 'two'}),
          modifyDate: modifyDate,
        )),
        _toExpect('''
<x:xmpmeta xmlns:x="adobe:ns:meta/">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about="uuid:faf5bdd5-ba3d-11da-ad31-d33d75182f1b"
      xmlns:dc="http://purl.org/dc/elements/1.1/"
      xmlns:xmp="http://ns.adobe.com/xap/1.0/"
      xmp:MetadataDate="$xmpDate"
      xmp:ModifyDate="$xmpDate">
      <xmp:Rating>5</xmp:Rating>
      <dc:subject>
        <rdf:Bag>
          <rdf:li>one</rdf:li>
          <rdf:li>two</rdf:li>
        </rdf:Bag>
      </dc:subject>
    </rdf:Description>
    <rdf:Description xmlns:MicrosoftPhoto="http://ns.microsoft.com/photo/1.0/" rdf:about="uuid:faf5bdd5-ba3d-11da-ad31-d33d75182f1b">
      <MicrosoftPhoto:Rating>99</MicrosoftPhoto:Rating>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>
'''));
  });

  test('Set tags to XMP with subjects only', () async {
    final modifyDate = DateTime.now();
    final xmpDate = XMP.toXmpDate(modifyDate);

    expect(
        _toExpect(await XMP.edit(
          inSubjects,
          toolkit,
          (descriptions) => ExtraAvesEntryMetadataEdition.editTagsXmp(descriptions, {'one', 'two'}),
          modifyDate: modifyDate,
        )),
        _toExpect('''
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core Test.SNAPSHOT">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about=""
      xmlns:dc="http://purl.org/dc/elements/1.1/"
      xmlns:xmp="http://ns.adobe.com/xap/1.0/"
      xmp:MetadataDate="$xmpDate"
      xmp:ModifyDate="$xmpDate">
      <dc:subject>
        <rdf:Bag>
          <rdf:li>one</rdf:li>
          <rdf:li>two</rdf:li>
        </rdf:Bag>
      </dc:subject>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>
'''));
  });

  test('Remove tags from XMP with subjects only', () async {
    expect(
        _toExpect(await XMP.edit(
          inSubjects,
          toolkit,
          (descriptions) => ExtraAvesEntryMetadataEdition.editTagsXmp(descriptions, {}),
        )),
        _toExpect(null));
  });

  test('Remove tags from XMP with subjects and creator', () async {
    final modifyDate = DateTime.now();
    final xmpDate = XMP.toXmpDate(modifyDate);

    expect(
        _toExpect(await XMP.edit(
          inSubjectsCreator,
          toolkit,
          (descriptions) => ExtraAvesEntryMetadataEdition.editTagsXmp(descriptions, {}),
          modifyDate: modifyDate,
        )),
        _toExpect('''
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core Test.SNAPSHOT">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about=""
      xmlns:dc="http://purl.org/dc/elements/1.1/"
      xmlns:xmp="http://ns.adobe.com/xap/1.0/"
      xmp:MetadataDate="$xmpDate"
      xmp:ModifyDate="$xmpDate">
      <dc:creator>
        <rdf:Seq>
          <rdf:li>c</rdf:li>
        </rdf:Seq>
      </dc:creator>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>
'''));
  });

  test('Set rating without existing XMP', () async {
    final modifyDate = DateTime.now();
    final xmpDate = XMP.toXmpDate(modifyDate);

    expect(
        _toExpect(await XMP.edit(
          null,
          toolkit,
          (descriptions) => ExtraAvesEntryMetadataEdition.editRatingXmp(descriptions, 3),
          modifyDate: modifyDate,
        )),
        _toExpect('''
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="$toolkit">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about=""
      xmlns:xmp="http://ns.adobe.com/xap/1.0/"
      xmp:Rating="3"
      xmp:MetadataDate="$xmpDate"
      xmp:ModifyDate="$xmpDate" />
  </rdf:RDF>
</x:xmpmeta>
'''));
  });

  test('Set rating to XMP with ratings (multiple descriptions)', () async {
    final modifyDate = DateTime.now();
    final xmpDate = XMP.toXmpDate(modifyDate);

    expect(
        _toExpect(await XMP.edit(
          inMultiDescriptionRatings,
          toolkit,
          (descriptions) => ExtraAvesEntryMetadataEdition.editRatingXmp(descriptions, 3),
          modifyDate: modifyDate,
        )),
        _toExpect('''
<x:xmpmeta xmlns:x="adobe:ns:meta/">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about="uuid:faf5bdd5-ba3d-11da-ad31-d33d75182f1b"
      xmlns:MicrosoftPhoto="http://ns.microsoft.com/photo/1.0/"
      xmlns:xmp="http://ns.adobe.com/xap/1.0/"
      MicrosoftPhoto:Rating="50"
      xmp:Rating="3"
      xmp:MetadataDate="$xmpDate"
      xmp:ModifyDate="$xmpDate" />
  </rdf:RDF>
</x:xmpmeta>
'''));
  });

  test('Set rating to XMP with rating attribute', () async {
    final modifyDate = DateTime.now();
    final xmpDate = XMP.toXmpDate(modifyDate);

    expect(
        _toExpect(await XMP.edit(
          inRatingAttribute,
          toolkit,
          (descriptions) => ExtraAvesEntryMetadataEdition.editRatingXmp(descriptions, 3),
          modifyDate: modifyDate,
        )),
        _toExpect('''
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core Test.SNAPSHOT">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about=""
      xmlns:xmp="http://ns.adobe.com/xap/1.0/"
      xmp:Rating="3"
      xmp:MetadataDate="$xmpDate"
      xmp:ModifyDate="$xmpDate" />
  </rdf:RDF>
</x:xmpmeta>
'''));
  });

  test('Set rating to XMP with rating element', () async {
    final modifyDate = DateTime.now();
    final xmpDate = XMP.toXmpDate(modifyDate);

    expect(
        _toExpect(await XMP.edit(
          inRatingElement,
          toolkit,
          (descriptions) => ExtraAvesEntryMetadataEdition.editRatingXmp(descriptions, 3),
          modifyDate: modifyDate,
        )),
        _toExpect('''
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core Test.SNAPSHOT">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about=""
      xmlns:xmp="http://ns.adobe.com/xap/1.0/"
      xmp:Rating="3"
      xmp:MetadataDate="$xmpDate"
      xmp:ModifyDate="$xmpDate" />
  </rdf:RDF>
</x:xmpmeta>
'''));
  });

  test('Set rating to XMP with subjects only', () async {
    final modifyDate = DateTime.now();
    final xmpDate = XMP.toXmpDate(modifyDate);

    expect(
        _toExpect(await XMP.edit(
          inSubjects,
          toolkit,
          (descriptions) => ExtraAvesEntryMetadataEdition.editRatingXmp(descriptions, 3),
          modifyDate: modifyDate,
        )),
        _toExpect('''
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core Test.SNAPSHOT">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about=""
      xmlns:dc="http://purl.org/dc/elements/1.1/"
      xmlns:xmp="http://ns.adobe.com/xap/1.0/"
      xmp:Rating="3"
      xmp:MetadataDate="$xmpDate"
      xmp:ModifyDate="$xmpDate">
      <dc:subject>
        <rdf:Bag>
          <rdf:li>the king</rdf:li>
        </rdf:Bag>
      </dc:subject>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>
'''));
  });

  test('Remove rating from XMP with subjects only', () async {
    final modifyDate = DateTime.now();

    expect(
        _toExpect(await XMP.edit(
          inSubjects,
          toolkit,
          (descriptions) => ExtraAvesEntryMetadataEdition.editRatingXmp(descriptions, null),
          modifyDate: modifyDate,
        )),
        _toExpect(inSubjects));
  });

  test('Remove trailer media info from XMP with micro video', () async {
    final modifyDate = DateTime.now();

    expect(
        _toExpect(await XMP.edit(
          inMotionPhotoMicroVideo,
          toolkit,
          ExtraAvesEntryMetadataEdition.removeContainerXmp,
          modifyDate: modifyDate,
        )),
        _toExpect(null));
  });

  test('Remove trailer media info from XMP with container', () async {
    final modifyDate = DateTime.now();

    expect(
        _toExpect(await XMP.edit(
          inMotionPhotoContainer,
          toolkit,
          ExtraAvesEntryMetadataEdition.removeContainerXmp,
          modifyDate: modifyDate,
        )),
        _toExpect(null));
  });

  test('Remove trailer media info from XMP with no related metadata', () async {
    final modifyDate = DateTime.now();

    expect(
        _toExpect(await XMP.edit(
          inSubjects,
          toolkit,
          ExtraAvesEntryMetadataEdition.removeContainerXmp,
          modifyDate: modifyDate,
        )),
        _toExpect(inSubjects));
  });

  test('Remove rating from XMP with ratings (multiple descriptions)', () async {
    final modifyDate = DateTime.now();

    expect(
        _toExpect(await XMP.edit(
          inMultiDescriptionRatings,
          toolkit,
          (descriptions) => ExtraAvesEntryMetadataEdition.editRatingXmp(descriptions, null),
          modifyDate: modifyDate,
        )),
        _toExpect(null));
  });
}
